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.
If you came here before just note that I’m nowadays using an improved solution.
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 (Outdated)
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 https://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 https://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.
My Solution (Improved)
Basically the above advices of tweaking your IDE stay true.
Here’s the solution I’m currently using:
import de.caff.annotation.NotNull;
import de.caff.annotation.Nullable;
import de.caff.generics.Indexable;
import de.caff.generics.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;
/**
* The values of this enum.
* Use this instead of {@link #values()} because it does not require internal array copying.
*/
public static final Indexable<JoinStyle> VALUES = Indexable.viewArray(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);
}
}
The main advantage is that anyone can now use the VALUES
constant instead of values()
.
The code makes usage of my Indexable
class, which is basically just a read-only substitute
for java.util.List
. Indexable
is included in the generics
module of my de·caff Commons.
But if you want to avoid including my lib you can also use an unmodifiable list instead:
/**
* The values of this enum.
* Use this instead of {@link #values()} because it does not require internal array copying.
*/
public static final List<Direction> VALUES = Collections.unmodifiableList(Arrays.asList(values())):
My IntelliJ IDEA template for enums now looks:
#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.generics.Indexable;
import de.caff.generics.Enums;
#parse("Class Comment.java")
public enum ${NAME} {
;
/**
* The values of this enum.
* Use this instead of {@link #values()} because it does not require internal array copying.
*/
@NotNull
public static final Indexable<${NAME}> VALUES = Indexable.viewArray(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);
}
}