Enum values() Method Should Be Avoided in Time-Critical Code

Enum.values() returns an array. Because you can change the values in the array, the implementation is forced to clone the array internally on each call.

The Problem

Generally I’m a big fan of Java enums. But the design decision that values() returns an array of the possible enum values forces it to clone the array on each call. Because there is no way to avoid that a caller changes array entries, arrays are generally a very bad choice for constants.

I haven’t checked, but the implementation of an enum probably creates and populates an array with the enum values during class load. Although this array is a constant its not possible to make it public as this would allow any user to change its entries. So the enum designer decided to add the method values() which just returns a clone of this array.

private static final MyEnum[] VALUES;
static {
  // populate the constant array directly after class load
  // ...
}

public MyEnum[] values()
{
  return VALUES.clone();
}

This is no real problem in cases where you check for an enum value once as a reaction of a user input, but when you call the values() method often you should better cache its result and access the cached values.

A constant java.util.List backed up by an unmodifiable list would have been a much better design choice, because then a constant would be good enough, the method would not be necessary.

My Solution

The file formats DXF and DWG I handle in my viewer project make a lot of use of integer constants. In most cases they are converted to enums in my code, so during read it happens quite often that an integer has to be converted into an enum.

Luckily enums in Java are very powerful. So the best way I found is to extend my enums with static methods which handle the integer to enum value conversion. In my case this is especially useful because the integer constants appearing in the files do not always run from 0 to a maximum value like the enum ordinals. So the method can take care of that, too, where necessary, and I still have the same interface.

A typical enum in my lib looks like

import de.caff.annotation.NotNull;
import de.caff.annotation.Nullable;
import de.caff.util.Enums;

/**
 * Line join style.
 *
 * @author <a href="mailto:rammi@caff.de">Rammi</a>
 * @since 2015 11 15
 */
public enum JoinStyle
{
  /** No specific style. */
  None,
  /** Round join. */
  Round,
  /** Angle join. */
  Angle,
  /** Flat join. */
  Flat;

  /**
   * Cached values.
   */
  private static final JoinStyle[] VALUES = values();

  /**
   * Get the JoinStyle enum associated with the given internal value.
   *
   * @param internalValue internal value
   * @return associated enum value, or {@code null} if there is none
   */
  @Nullable
  public static JoinStyle fromInternal(int internalValue)
  {
    return Enums.getEnumFromOrdinal(VALUES, internalValue);
  }

  /**
   * Get the JoinStyle enum associated with the given internal value.
   *
   * @param internalValue internal value
   * @param defaultValue  default value
   * @return associated enum value, or {@code defaultValue} if there is none
   */
  @NotNull
  public static JoinStyle fromInternal(int internalValue, @NotNull JoinStyle defaultValue)
  {
    return Enums.getEnumFromOrdinal(VALUES, internalValue, defaultValue);
  }
}

As you can see the values are cached once in the VALUES constant. Then the two fromInternal() methods convert integers to enums, one without and one with a default value. The latter can promise that it’ll never return null if it gets a default value which is not null.

Make a Template

Obviously adding this code to each enum is quite tedious. I’m using IntelliJ IDEA which allows to define templates for classes it creates. So I extended its template for enum creation. It now looks like

#parse("File Header.java")
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end

import de.caff.annotation.NotNull;
import de.caff.annotation.Nullable;
import de.caff.util.Enums;

#parse("Class Comment.java")
public enum ${NAME} {
  ;
  
  /** Cached values. */
  private static final ${NAME}[] VALUES = values();

  /**
   * Get the ${NAME} enum associated with the given internal value.
   * @param internalValue internal value
   * @return associated enum value, or {@code null} if there is none
   */
  @Nullable
  public static ${NAME} fromInternal(int internalValue)
  {
    return Enums.getEnumFromOrdinal(VALUES, internalValue);
  }

  /**
   * Get the ${NAME} enum associated with the given internal value.
   * @param internalValue internal value
   * @param defaultValue  default value
   * @return associated enum value, or {@code defaultValue} if there is none
   */
  @NotNull
  public static ${NAME} fromInternal(int internalValue, @NotNull ${NAME} defaultValue)
  {
    return Enums.getEnumFromOrdinal(VALUES, internalValue, defaultValue);
  }
}

File Header.java is already a standard template of IDEA, which I adapted to my needs. Class Comment.java was added by me so I can have the same basic class comment in all class-like templates (class, interface, enum etc.). In case you are interested it looks like

/**
 *
 * @author <a href="mailto:rammi@caff.de">Rammi</a>
 * @since ${YEAR} ${MONTH} ${DAY}
 */

Enums Utility Class

The code also makes use of a utility class called Enums (similar to java.util.Arrays or java.util.Collections). Here it is in its completeness:

// ============================================================================
// COPYRIGHT NOTICE
// ----------------------------------------------------------------------------
// (This is the open source ISC license, see
// http://en.wikipedia.org/wiki/ISC_license
// for more info)
//
// Copyright © 2015  Andreas M. Rammelt <rammi@caff.de>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//=============================================================================
// Created:            04.11.15 13:06
//=============================================================================
package de.caff.util;

import de.caff.annotation.NotNull;
import de.caff.annotation.Nullable;
import de.caff.util.debug.Debug;

/**
 * Toolbox for enums.
 *
 * @author <a href="mailto:rammi@caff.de">Rammi</a>
 */
public class Enums
{
  /** Don't create. */
  private Enums() {}

  /**
   * Get an enum value from its ordinal.
   * @param values  enum values with ordinal indices
   * @param ordinal enum ordinal
   * @param <E> type of returned value, usually an enum but this is not enforced
   * @return associated value or {@code null} if there is no matching value
   */
  @Nullable
  public static <E> E getEnumFromOrdinal(@NotNull E[] values,
                                         int ordinal)
  {
    return ordinal >= 0 && ordinal < values.length
            ? values[ordinal]
            : null;
  }

  /**
   * Get an enum value from its ordinal.
   * @param values  enum values with ordinal indices
   * @param ordinal enum ordinal
   * @param <E> type of returned value, usually an enum but this is not enforced
   * @return associated value or {@code defaultValue} if there is no matching value
   */
  @NotNull
  public static <E> E getEnumFromOrdinal(@NotNull E[] values,
                                         int ordinal,
                                         @NotNull E defaultValue)
  {
    final E result = getEnumFromOrdinal(values, ordinal);
    if (result == null) {
      Debug.warn("Fixing non-matched enum ordinal %0 to %1", ordinal, defaultValue);
      return defaultValue;
    }           
    return result;
  }
}

Although intended to be used for enums the methods will accept any non-primitive array type, e.g. a String array. If you want to use it just copy it and remove the import of the Debug class and the line using it in the last method.

The situation with the @NotNull/@Nullable annotations is quite a mess up to Java 8 (which various of my customers haven’t reached yet), so I just invented my own like many others (which basically created the mess). You can use your own choice, but preferably the ones which came with Java8. Or just remove them from the code if you don’t like them.

You may even use mine:

@NotNull:

// ============================================================================
// COPYRIGHT NOTICE
// ----------------------------------------------------------------------------
// (This is the open source ISC license, see
// http://en.wikipedia.org/wiki/ISC_license
// for more info)
//
// Copyright © 2012  Andreas M. Rammelt <rammi@caff.de>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//=============================================================================
// Latest version on http://caff.de/generics/
//=============================================================================


package de.caff.annotation;

import java.lang.annotation.*;

/**
 * Annotation which marks a return value or a parameter as never being {@code null}.
 * When JSR-305 is approved, we should switch there.
 */
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
public @interface NotNull
{
}

@Nullable:

// ============================================================================
// COPYRIGHT NOTICE
// ----------------------------------------------------------------------------
// (This is the open source ISC license, see
// http://en.wikipedia.org/wiki/ISC_license
// for more info)
//
// Copyright © 2012  Andreas M. Rammelt <rammi@caff.de>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//=============================================================================
// Latest version on http://caff.de/generics/
//=============================================================================

package de.caff.annotation;

import java.lang.annotation.*;

/**
 * Annotation which marks a return value or a parameter as possibly being {@code null}.
 * When JSR-305 is approved, we should switch there.
 */
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
public @interface Nullable
{
}

In IDEA it was necessary to configure its settings to make these annotations known. For that open the settings, search for notnull and find in Editor > Inspections an entry called @NotNull/@Nullable problems. Click on that entry, and then on the Config Annotations button where you can add both annotations to the appropriate list and make them the defaults if you indent to use them in your own code, too.