JDK-8073381 : need API to get enum's values without creating a new array
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.lang
  • Priority: P3
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2015-02-18
  • Updated: 2018-04-06
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Related Reports
Duplicate :  
Relates :  
Relates :  
Relates :  
Relates :  
Every enum type provides a values() method that returns an array of the enum constants. Since arrays are mutable, the returned array must be a freshly created copy of the internal array.

It would be nice to have an API that returned an object that represented the enum constants, but avoided having to make a copy of the array just for defensive purposes. For some enum type E this could be something like Stream<E> or perhaps List<E>, with the latter having properties of being random-access and immutable.

Something to consider is having two "flavors" or this method, one on the enum type itself, and another on java.lang.Enum or java.lang.Class that would provide the values for an enum given its class. This is similar to the situation with values(), which occurs directly on the enum type, and which has an equivalent java.lang.Class.getEnumConstants().
It seems like there are a bunch of moving parts here. Given that a private factory is helpful in other cases and will get implemented at some point, it could be reused for the case above where Paul mentioned creation of "an unmodifiable list without the array copy."

[~smarks] I don't think that's necessary here. You're doing it once, on first call. Thereafter its a constant load. You're saving N copies, at the cost of one copy. Optimizing down to zero copies is cool but is the short end of the lever.

On the List implementation side, it's clearly necessary to have a private factory of some sort that takes an array of values and simply stores a reference to it without copying. The caller must be trusted to have populated the array properly before its assignment to the @Stable field, and of course the caller must be trusted never to modify the array thereafter. Such a private factory is necessary for avoiding excess copies by the copyOf() factory method and the toUnmodifiableList collector.

We may need a private BSM on Enum accepting the enum concrete class. Deferring to List.of via ConstantBootstraps.invoke (or later directly, if supported) is nice but will require that the enum values are represented in the constant pool as well. This will require a condy for each argument, potentially increasing the class file bytes, and resolution time (even though it's a one time hit, it might be a startup hit). A private BSM may also have the advantage of avoiding a copy of the values array, if it can create an unmodifable list without the array copy. However, a condy could be avoided if javac used an unmodifiable List instead of an array to hold the sequence of values in its synthetic static final field, at the cost of a very marginal increase in heap size. Another strong motivation to use condy, in addition to returning something that is a constant, is to make static initialization lazy. This could be applied to the enum's static initializer that currently allocates and initializes the values array (held in a synthetic static final field). Care might be required if such a change induced VM bootstrap circularity issues, certain enums may need to be off-limits for such translations (e.g. those used by VarHandle).

More specifically, it should be created with a condy (so, laziness) that does a List.of(values()), so that it picks up the @Stable-ness of the List.of() implementation. (Currently, we can't call an ordinary factory like List.of() as a condy bootstrap; this is a limitation that will hopefully be removed soon.)

After further discussion, we're back to a single method (e.g. valueList or another name) that returns an unmodifiable List. This can easily be streamed, the size gotten with size(), and ordinal converted to enum value with get(i). A wrinkle is that the List would be instantiated and cached lazily using condy instead of conventional Java language constructs.

Observation: it is possible to remove the internal static final VALUE[] array field if we introduce a notion of an array view over a fields of a class. This is possible to construct with Unsafe (and would require a special spliterator for the Stream returning method, and a sneaky way of cloning with an unsafe memory copy for the values() method). In combination with removing enum specific fields on Class and replacing with static final ClassValue on Enum this should further reduce memory footprint.

Paul and I chatted about this a bit. Exposing fromOrdinal/count/stream seems quite reasonable in place of exposing a List, but a better name than fromOrdinal would be nice. :-) Some additional points: * It would be nice if every static method that is synthesized on enum classes has a counterpart on java.lang.Enum itself, with a leading Class argument. The valueOf() method is this way, but values() is not. * Every Class -- even those that don't represent enums -- has fields enumConstants (an array) and enumConstantDirectory (a HashMap). Of course, they're used only for enum classes, but they take up space in every Class object. These should be migrated to use ClassValue. * The enumConstants array and enumConstantDirectory HashMap could be replaced with some kind of compact bidirectional map. This might alleviate some initialization issues caused by bringing in List early in startup. It might be overkill though. An alternative might be to have something like Arrays.asUnmodifiableList() that does the obvious thing. * java.util.EnumSet uses a shared secret to get access to the enumConstants array, avoiding making a copy. The proposed APIs (probably) expose enough information that the shared secret can probably be ripped out. * The synthesized methods are specified in JLS 8.9, which is proper, but it would be convenient if they were also described in java.lang.Enum itself. They sort-of are today, but in the Enum.valueOf() doc. This is an odd place. This material should be promoted into the Enum class doc.

These are all reasonable; I'd suggest count() instead of size(), as it reads better at the call site.

Enum.stream().skip(ord).findFirst() :-) The patch shows it's generally quite easy to return a Stream or a List (the latter requires more work to leverage an efficient unmodifiable List implementation). A List returning method could support returning a constant, ideally with lazy allocation (reduce memory and perhaps initialization costs), but that requires more code in the generated enum class. Enum feels very frozen "arrayish", if we had budget for more than one method i might prefer: T fromOrdinal(int i) int size() Stream<T> stream()

Before we get too far with this... The stream() idea is interesting, but it doesn't help the use case that values() supports, namely, getting the enum constant that corresponds to a given ordinal value (int). This can be accomplished with MyEnum.values()[ord] at the cost of allocating and array to be thrown away. Perhaps ideally we'd like an API that returns a frozen array. Failing that, an immutable list would work: MyEnum.valueList().get(ord) Adding a stream() call doesn't help this case. You'd have to collect this into an array, which is allocated and initialized and thrown away, just like what values() does. A stream() call is probably useful for other things though. Then again, if we had valueList() or whatever, a stream could be gotten by calling valueList().stream().

Here is a patch to the java compiler to support Enum.stream: http://cr.openjdk.java.net/~psandoz/jdk10/JDK-8073381-enum-stream/webrev/

Or a stream() method.

Another possibility might be something like forEach(Consumer<E> consumer)

If an API were added that returned a List, this would satisfy JDK-6234270. One could write: EnumClass.valueList().get(k) to get the k'th enum value.