Enum#values() returns a new array copy every time it is called. So the cheapest way to access all constants of an enum is to memoize/cache this array (or collection if you need to guarantee immutability).
So if you control all clients of your enum, the following works without copying the array (pedantic mode: it is still copied, but only exactly once when the class is loaded):
enum YourEnum {
FIRST,
SECOND,
;
public static final YourEnum[] ALL_VALUES = values();
}
(the array is still mutable, so if you cannot guarantee that clients won't modify this array, read on).
If you cannot control the clients, you must provide an immutable collection. Iterating collections might incur slight overhead for the Iterator instance, but if you index into a List, you can avoid that cost too.
enum YourEnum {
FIRST,
SECOND,
;
public static final List<YourEnum> ALL_VALUES = List.of(values());
}
for (final YourEnum value : YourEnum.ALL_VALUES) { // potential Iterator allocation
// ...
}
for (int i = 0; i < YourEnum.ALL_VALUES.size(); ++i) { // guaranteed zero-allocation code
final YourEnum value = YourEnum.ALL_VALUES[i];
// ...
}
Usually all of that does not matter, but if you are working in a resource-constrained environment or are accessing/iterating all enum constants in a hot path of your code it might, so it's good to know your way around.
I wrote a small JMH benchmark which loops through all constants of a enum via different access methods (values(), a cached copy of the values, values copied to a list, and EnumSet.allof) with enums of different sizes (from a "tiny" enum comprising 3 constants to a "huge" enum comprising 256 constants).
Enum sizes:
- Tiny: 3 constants
- Small: 8 constants
- Medium: 24 constants
- Large: 96 constants
- Huge: 256 constants
Here is the quick summary:
- The cached array is consistently the fastest (20% for tiny enums and 30% for huge enums; except for "small" enums → read on). Note that for the tiny enum, there's almost no difference between the baseline and iterating the 3 enum constants.
EnumSet.allOf is consistently the slowest (2 to 5 times slower than the rest)
- Allocations of the EnumSet are negligible and only become measurable from the "large" enum (96 values) and up
- It does not matter whether you use an indexed for loop or an enhanced for loop to iterate the List containing the enum constants (this was a surprising result to me!)
- For "large" enums and up, iterating the List is 2 times slower than iterating the array
- For "small" enums (8 constants), the inverse could be observed: suddenly, iterating the array takes twice as long compared to the list (surprise #2! I don't know why this happens/happened)
Enum#values has a constant overhead of 4 * N + 16 bytes per invocation (4 bytes per enum constant plus 16 bytes for the array itself). For "huge" enums (256 constants), this allocates 1 kb of memory per invocation.
EnumSet for enums with more than 64 constants has an overhead of ceil(N/64.0)*8.0 + 16 bytes (1 bit per constant, rounded up to the next multiple of 8 plus 16 bytes for the backing array). Enums with up to 64 constants only require a single long to store the bitset.
- Iterating the List seems to be slower than iterating
.values(), even though values requires copying the array which the list does not (surprise #3).
Benchmarks (you can find the source at the bottom):
- (
baseline: baseline test, only "selects" the enum of the correct size)
values: access via Enum#values() method, enhanced for loop
array: access via cached array, enhanced for loop (for (var x : arr))
arrayIndexed: access via cached array, indexed for loop (arr[i])
listIterator: access via cached List, enhanced for loop
listIndexed: access via cached List, indexed for loop
enumSet: dynamic construction via EnumSet.allof, enhanced for loop
I ran the benchmarks with JMH 1.37 and Java 21 on my trusty old laptop with an i7-3740QM. I set 2 forks, 4 warmup and 4 measurement iterations, with 4 seconds each. The absolute numbers are not all that important, what matters is the relative difference.
Timing results (smaller is faster):
Benchmark (enumSize) Mode Cnt Score Error Units
EnumBenchmark.arrayIndexed tiny avgt 8 4.351 ± 0.042 ns/op
EnumBenchmark.listIterator tiny avgt 8 4.359 ± 0.076 ns/op
EnumBenchmark.listIndexed tiny avgt 8 4.457 ± 0.281 ns/op
EnumBenchmark.baseline tiny avgt 8 4.618 ± 0.054 ns/op
EnumBenchmark.enumSet tiny avgt 8 12.498 ± 0.248 ns/op
EnumBenchmark.values tiny avgt 8 5.790 ± 0.101 ns/op
EnumBenchmark.array tiny avgt 8 4.471 ± 0.155 ns/op
EnumBenchmark.arrayIndexed small avgt 8 8.088 ± 0.140 ns/op
EnumBenchmark.listIterator small avgt 8 4.654 ± 0.069 ns/op
EnumBenchmark.listIndexed small avgt 8 4.614 ± 0.061 ns/op
EnumBenchmark.baseline small avgt 8 4.626 ± 0.059 ns/op
EnumBenchmark.enumSet small avgt 8 22.563 ± 0.271 ns/op
EnumBenchmark.values small avgt 8 11.386 ± 0.120 ns/op
EnumBenchmark.array small avgt 8 8.095 ± 0.097 ns/op
EnumBenchmark.arrayIndexed medium avgt 8 16.247 ± 0.157 ns/op
EnumBenchmark.listIterator medium avgt 8 27.622 ± 0.286 ns/op
EnumBenchmark.listIndexed medium avgt 8 27.749 ± 0.390 ns/op
EnumBenchmark.baseline medium avgt 8 4.617 ± 0.059 ns/op
EnumBenchmark.enumSet medium avgt 8 58.943 ± 0.888 ns/op
EnumBenchmark.values medium avgt 8 21.558 ± 2.970 ns/op
EnumBenchmark.array medium avgt 8 16.269 ± 0.258 ns/op
EnumBenchmark.arrayIndexed large avgt 8 47.695 ± 0.709 ns/op
EnumBenchmark.listIterator large avgt 8 95.920 ± 2.063 ns/op
EnumBenchmark.listIndexed large avgt 8 96.209 ± 2.917 ns/op
EnumBenchmark.baseline large avgt 8 4.913 ± 0.576 ns/op
EnumBenchmark.enumSet large avgt 8 221.370 ± 8.615 ns/op
EnumBenchmark.values large avgt 8 58.391 ± 1.582 ns/op
EnumBenchmark.array large avgt 8 47.769 ± 0.673 ns/op
EnumBenchmark.arrayIndexed huge avgt 8 112.918 ± 1.043 ns/op
EnumBenchmark.listIterator huge avgt 8 224.763 ± 3.812 ns/op
EnumBenchmark.listIndexed huge avgt 8 224.339 ± 3.853 ns/op
EnumBenchmark.baseline huge avgt 8 4.346 ± 0.070 ns/op
EnumBenchmark.enumSet huge avgt 8 571.340 ± 17.533 ns/op
EnumBenchmark.values huge avgt 8 168.228 ± 2.954 ns/op
EnumBenchmark.array huge avgt 8 112.483 ± 0.922 ns/op
Normalized memory allocations (allocations per benchmark invocation, smaller is better):
Benchmark (enumSize) Mode Cnt Score Error Units
EnumBenchmark.arrayIndexed:gc.alloc.rate.norm tiny avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.listIterator:gc.alloc.rate.norm tiny avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.listIndexed:gc.alloc.rate.norm tiny avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.baseline:gc.alloc.rate.norm tiny avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.enumSet:gc.alloc.rate.norm tiny avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.values:gc.alloc.rate.norm tiny avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.array:gc.alloc.rate.norm tiny avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.arrayIndexed:gc.alloc.rate.norm small avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.listIterator:gc.alloc.rate.norm small avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.listIndexed:gc.alloc.rate.norm small avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.baseline:gc.alloc.rate.norm small avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.enumSet:gc.alloc.rate.norm small avgt 8 ≈ 10⁻⁴ B/op
EnumBenchmark.values:gc.alloc.rate.norm small avgt 8 48.000 ± 0.001 B/op
EnumBenchmark.array:gc.alloc.rate.norm small avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.arrayIndexed:gc.alloc.rate.norm medium avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.listIterator:gc.alloc.rate.norm medium avgt 8 ≈ 10⁻⁴ B/op
EnumBenchmark.listIndexed:gc.alloc.rate.norm medium avgt 8 ≈ 10⁻⁴ B/op
EnumBenchmark.baseline:gc.alloc.rate.norm medium avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.enumSet:gc.alloc.rate.norm medium avgt 8 ≈ 10⁻⁴ B/op
EnumBenchmark.values:gc.alloc.rate.norm medium avgt 8 112.000 ± 0.001 B/op
EnumBenchmark.array:gc.alloc.rate.norm medium avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.arrayIndexed:gc.alloc.rate.norm large avgt 8 ≈ 10⁻⁴ B/op
EnumBenchmark.listIterator:gc.alloc.rate.norm large avgt 8 ≈ 10⁻⁴ B/op
EnumBenchmark.listIndexed:gc.alloc.rate.norm large avgt 8 ≈ 10⁻⁴ B/op
EnumBenchmark.baseline:gc.alloc.rate.norm large avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.enumSet:gc.alloc.rate.norm large avgt 8 32.000 ± 0.001 B/op
EnumBenchmark.values:gc.alloc.rate.norm large avgt 8 400.000 ± 0.001 B/op
EnumBenchmark.array:gc.alloc.rate.norm large avgt 8 ≈ 10⁻⁴ B/op
EnumBenchmark.arrayIndexed:gc.alloc.rate.norm huge avgt 8 ≈ 10⁻⁴ B/op
EnumBenchmark.listIterator:gc.alloc.rate.norm huge avgt 8 ≈ 10⁻³ B/op
EnumBenchmark.listIndexed:gc.alloc.rate.norm huge avgt 8 ≈ 10⁻³ B/op
EnumBenchmark.baseline:gc.alloc.rate.norm huge avgt 8 ≈ 10⁻⁵ B/op
EnumBenchmark.enumSet:gc.alloc.rate.norm huge avgt 8 48.001 ± 0.001 B/op
EnumBenchmark.values:gc.alloc.rate.norm huge avgt 8 1040.000 ± 0.001 B/op
EnumBenchmark.array:gc.alloc.rate.norm huge avgt 8 ≈ 10⁻⁴ B/op
JMH info:
# JMH version: 1.37
# VM version: JDK 21.0.9, OpenJDK 64-Bit Server VM, 21.0.9+10-Ubuntu-125.10
# VM invoker: /usr/lib/jvm/java-21-openjdk-amd64/bin/java
# VM options: -javaagent:...
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 4 iterations, 4 s each
# Measurement: 4 iterations, 4 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
And the full benchmark code:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 2)
@Warmup(iterations = EnumValuesBenchmark.ITERATIONS, time = EnumValuesBenchmark.TIME, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = EnumValuesBenchmark.ITERATIONS, time = EnumValuesBenchmark.TIME, timeUnit = TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class EnumBenchmark {
static final int ITERATIONS = 4;
static final int TIME = 4;
@Param({"tiny", "small", "medium", "large", "huge"})
public String enumSize;
private String baselineSwitch() {
return switch (enumSize) {
case "tiny" -> "TinyEnum";
case "small" -> "SmallEnum";
case "medium" -> "MediumEnum";
case "large" -> "LargeEnum";
case "huge" -> "HugeEnum";
default -> throw new IllegalStateException();
};
}
private Enum<?>[] enumValues() {
return switch (enumSize) {
case "tiny" -> TinyEnum.values();
case "small" -> SmallEnum.values();
case "medium" -> MediumEnum.values();
case "large" -> LargeEnum.values();
case "huge" -> HugeEnum.values();
default -> throw new IllegalStateException();
};
}
private Enum<?>[] enumArray() {
return switch (enumSize) {
case "tiny" -> TinyEnum.VALUES_ARRAY;
case "small" -> SmallEnum.VALUES_ARRAY;
case "medium" -> MediumEnum.VALUES_ARRAY;
case "large" -> LargeEnum.VALUES_ARRAY;
case "huge" -> HugeEnum.VALUES_ARRAY;
default -> throw new IllegalStateException();
};
}
private List<? extends Enum<?>> enumList() {
return switch (enumSize) {
case "tiny" -> TinyEnum.VALUES_LIST;
case "small" -> SmallEnum.VALUES_LIST;
case "medium" -> MediumEnum.VALUES_LIST;
case "large" -> LargeEnum.VALUES_LIST;
case "huge" -> HugeEnum.VALUES_LIST;
default -> throw new IllegalStateException();
};
}
private EnumSet<? extends Enum<?>> enumSetAllOf() {
return switch (enumSize) {
case "tiny" -> EnumSet.allOf(TinyEnum.class);
case "small" -> EnumSet.allOf(SmallEnum.class);
case "medium" -> EnumSet.allOf(MediumEnum.class);
case "large" -> EnumSet.allOf(LargeEnum.class);
case "huge" -> EnumSet.allOf(HugeEnum.class);
default -> throw new IllegalStateException();
};
}
// general
@Benchmark
public Object baseline() {
// measure the cost of switching on the enum size
return baselineSwitch();
}
@Benchmark
public long values() {
long result = 0L;
for (final var value : enumValues()) {
result ^= value.ordinal();
}
return result;
}
@Benchmark
public long array() {
long result = 0L;
for (final var value : enumArray()) {
result ^= value.ordinal();
}
return result;
}
@Benchmark
public long arrayIndexed() {
long result = 0L;
final Enum<?>[] array = enumArray();
for (int i = 0, len = array.length; i < len; i++) {
final var value = array[i];
result ^= value.ordinal();
}
return result;
}
@Benchmark
public long listIterator() {
long result = 0L;
for (final var value : enumList()) {
result ^= value.ordinal();
}
return result;
}
@Benchmark
public long listIndexed() {
long result = 0L;
final var values = enumList();
for (int i = 0; i < values.size(); ++i) {
final var value = values.get(i);
result ^= value.ordinal();
}
return result;
}
@Benchmark
public long enumSet() {
long result = 0L;
for (final var value : enumSetAllOf()) {
result ^= value.ordinal();
}
return result;
}
enum TinyEnum {
TRUE, FALSE, FILE_NOT_FOUND,
;
public static final TinyEnum[] VALUES_ARRAY = values();
public static final List<TinyEnum> VALUES_LIST = List.of(values());
}
enum SmallEnum {
NEVER, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY,
;
public static final SmallEnum[] VALUES_ARRAY = values();
public static final List<SmallEnum> VALUES_LIST = List.of(values());
}
enum MediumEnum {
VALUE_1, VALUE_2, VALUE_3, VALUE_4, VALUE_5, VALUE_6, VALUE_7, VALUE_8,
VALUE_9, VALUE_10, VALUE_11, VALUE_12, VALUE_13, VALUE_14, VALUE_15, VALUE_16,
VALUE_17, VALUE_18, VALUE_19, VALUE_20, VALUE_21, VALUE_22, VALUE_23, VALUE_24,
;
public static final MediumEnum[] VALUES_ARRAY = values();
public static final List<MediumEnum> VALUES_LIST = List.of(values());
}
enum LargeEnum {
VALUE_1, VALUE_2, VALUE_3, VALUE_4, VALUE_5, VALUE_6, VALUE_7, VALUE_8,
VALUE_9, VALUE_10, VALUE_11, VALUE_12, VALUE_13, VALUE_14, VALUE_15, VALUE_16,
VALUE_17, VALUE_18, VALUE_19, VALUE_20, VALUE_21, VALUE_22, VALUE_23, VALUE_24,
VALUE_25, VALUE_26, VALUE_27, VALUE_28, VALUE_29, VALUE_30, VALUE_31, VALUE_32,
VALUE_33, VALUE_34, VALUE_35, VALUE_36, VALUE_37, VALUE_38, VALUE_39, VALUE_40,
VALUE_41, VALUE_42, VALUE_43, VALUE_44, VALUE_45, VALUE_46, VALUE_47, VALUE_48,
VALUE_49, VALUE_50, VALUE_51, VALUE_52, VALUE_53, VALUE_54, VALUE_55, VALUE_56,
VALUE_57, VALUE_58, VALUE_59, VALUE_60, VALUE_61, VALUE_62, VALUE_63, VALUE_64,
VALUE_65, VALUE_66, VALUE_67, VALUE_68, VALUE_69, VALUE_70, VALUE_71, VALUE_72,
VALUE_73, VALUE_74, VALUE_75, VALUE_76, VALUE_77, VALUE_78, VALUE_79, VALUE_80,
VALUE_81, VALUE_82, VALUE_83, VALUE_84, VALUE_85, VALUE_86, VALUE_87, VALUE_88,
VALUE_89, VALUE_90, VALUE_91, VALUE_92, VALUE_93, VALUE_94, VALUE_95, VALUE_96,
;
public static final LargeEnum[] VALUES_ARRAY = values();
public static final List<LargeEnum> VALUES_LIST = List.of(values());
}
enum HugeEnum {
VALUE_1, VALUE_2, VALUE_3, VALUE_4, VALUE_5, VALUE_6, VALUE_7, VALUE_8,
VALUE_9, VALUE_10, VALUE_11, VALUE_12, VALUE_13, VALUE_14, VALUE_15, VALUE_16,
VALUE_17, VALUE_18, VALUE_19, VALUE_20, VALUE_21, VALUE_22, VALUE_23, VALUE_24,
VALUE_25, VALUE_26, VALUE_27, VALUE_28, VALUE_29, VALUE_30, VALUE_31, VALUE_32,
VALUE_33, VALUE_34, VALUE_35, VALUE_36, VALUE_37, VALUE_38, VALUE_39, VALUE_40,
VALUE_41, VALUE_42, VALUE_43, VALUE_44, VALUE_45, VALUE_46, VALUE_47, VALUE_48,
VALUE_49, VALUE_50, VALUE_51, VALUE_52, VALUE_53, VALUE_54, VALUE_55, VALUE_56,
VALUE_57, VALUE_58, VALUE_59, VALUE_60, VALUE_61, VALUE_62, VALUE_63, VALUE_64,
//
VALUE_65, VALUE_66, VALUE_67, VALUE_68, VALUE_69, VALUE_70, VALUE_71, VALUE_72,
VALUE_73, VALUE_74, VALUE_75, VALUE_76, VALUE_77, VALUE_78, VALUE_79, VALUE_80,
VALUE_81, VALUE_82, VALUE_83, VALUE_84, VALUE_85, VALUE_86, VALUE_87, VALUE_88,
VALUE_89, VALUE_90, VALUE_91, VALUE_92, VALUE_93, VALUE_94, VALUE_95, VALUE_96,
VALUE_97, VALUE_98, VALUE_99, VALUE_100, VALUE_101, VALUE_102, VALUE_103, VALUE_104,
VALUE_105, VALUE_106, VALUE_107, VALUE_108, VALUE_109, VALUE_110, VALUE_111, VALUE_112,
VALUE_113, VALUE_114, VALUE_115, VALUE_116, VALUE_117, VALUE_118, VALUE_119, VALUE_120,
VALUE_121, VALUE_122, VALUE_123, VALUE_124, VALUE_125, VALUE_126, VALUE_127, VALUE_128,
//
VALUE_129, VALUE_130, VALUE_131, VALUE_132, VALUE_133, VALUE_134, VALUE_135, VALUE_136,
VALUE_137, VALUE_138, VALUE_139, VALUE_140, VALUE_141, VALUE_142, VALUE_143, VALUE_144,
VALUE_145, VALUE_146, VALUE_147, VALUE_148, VALUE_149, VALUE_150, VALUE_151, VALUE_152,
VALUE_153, VALUE_154, VALUE_155, VALUE_156, VALUE_157, VALUE_158, VALUE_159, VALUE_160,
VALUE_161, VALUE_162, VALUE_163, VALUE_164, VALUE_165, VALUE_166, VALUE_167, VALUE_168,
VALUE_169, VALUE_170, VALUE_171, VALUE_172, VALUE_173, VALUE_174, VALUE_175, VALUE_176,
VALUE_177, VALUE_178, VALUE_179, VALUE_180, VALUE_181, VALUE_182, VALUE_183, VALUE_184,
VALUE_185, VALUE_186, VALUE_187, VALUE_188, VALUE_189, VALUE_190, VALUE_191, VALUE_192,
//
VALUE_193, VALUE_194, VALUE_195, VALUE_196, VALUE_197, VALUE_198, VALUE_199, VALUE_200,
VALUE_201, VALUE_202, VALUE_203, VALUE_204, VALUE_205, VALUE_206, VALUE_207, VALUE_208,
VALUE_209, VALUE_210, VALUE_211, VALUE_212, VALUE_213, VALUE_214, VALUE_215, VALUE_216,
VALUE_217, VALUE_218, VALUE_219, VALUE_220, VALUE_221, VALUE_222, VALUE_223, VALUE_224,
VALUE_225, VALUE_226, VALUE_227, VALUE_228, VALUE_229, VALUE_230, VALUE_231, VALUE_232,
VALUE_233, VALUE_234, VALUE_235, VALUE_236, VALUE_237, VALUE_238, VALUE_239, VALUE_240,
VALUE_241, VALUE_242, VALUE_243, VALUE_244, VALUE_245, VALUE_246, VALUE_247, VALUE_248,
VALUE_249, VALUE_250, VALUE_251, VALUE_252, VALUE_253, VALUE_254, VALUE_255, VALUE_256,
;
public static final HugeEnum[] VALUES_ARRAY = values();
public static final List<HugeEnum> VALUES_LIST = List.of(values());
}
}
Maybe somebody can provide an explanation for surprise #2 and #3? They are not what I expect, so either there is something happening in the JVM that I'm not aware of or my benchmark is flawed (or both!).