Enum values() Method Should Be Avoided in Time-Critical Code
Mon, May 16, 2016
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 Rammi
* @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
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.
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.