An enum is a kind of class where all instances are known and can be enumerated
By example, for a program they may be 3 ways to list files of a directory, either all files (ALL), either only the normal file (NORMAL) or only the directory (DIRECTORY)
enum FileListMode { ALL, NORMAL, DIRECTORY }
The enumerated instances are considered as constants thus can be accessed like any constant
System.out.println(FileListMode.ALL);
All enums inherits from the class java.lang.Enum
that defines two components
- name which is the name of the instance
- ordinal which is the index (starting at 0)
System.out.println(FileListMode.ALL.name());
System.out.println(FileListMode.ALL.ordinal());
equals()/hashCode() and toString() are inherited from java.lang.Enum
- equals() delegates to ==
- hashCode() returns ordinal
- toString() returns name
System.out.println(FileListMode.ALL.equals(FileListMode.NORMAL));
System.out.println(FileListMode.NORMAL);
System.out.println(FileListMode.DIRECTORY.hashCode());
Enum instances are comparable (their ordinal value is used) so ALL < NORMAL < DIRECTORY
System.out.println(FileListMode.ALL.compareTo(FileListMode.NORMAL) < 0);
Two supplementary static methods are generated by the compiler
- values() return an array of all instances
- valueOf(name) return the instance corresponding to the name or an exception
System.out.println(Arrays.toString(FileListMode.values()));
System.out.println(FileListMode.valueOf("ALL"));
System.out.println(FileListMode.valueOf("invalid"));
values()
returns a new cloned array at each invocation
so don't call it inside a loop :)
Unlike in C where enums are integers, enum in Java are full objects so they can have fields, constructors and methods defined after a semicolon at the end of the list of the instances
enum FileListMode {
ALL,
NORMAL,
DIRECTORY, // trailing comma is allowed
; // ends of the instances
public String shortName() {
return name().toLowerCase().substring(0, 3);
}
}
System.out.println(FileListMode.NORMAL.shortName());
System.out.println(FileListMode.DIRECTORY.shortName());
You can add fields if you want to associate specific values to the enum instances By example to convert from bits of an int to a set of modifier.
enum Modifier {
PUBLIC(1), FINAL(2), STATIC(4)
;
private final int value;
private Modifier(int value) {
this.value = value;
}
// avoid to calls values() several times
private static final List<Modifier> MODIFIERS = List.of(values());
static int modifiersAsInt(Modifier... modifiers) {
return Arrays.stream(modifiers).map(m -> m.value).reduce(0, (a, b) -> a | b);
}
static Set<Modifier> intAsModifierSet(int modifiers) {
return MODIFIERS.stream().filter(m -> (modifiers & m.value) != 0).collect(Collectors.toSet());
}
}
var modifiers = Modifier.modifiersAsInt(Modifier.PUBLIC, Modifier.STATIC);
System.out.println("int: " + modifiers);
var modifierSet = Modifier.intAsModifierSet(modifiers);
System.out.println("set: " + modifierSet);
The implementation of intAsModifierSet
can be a little more efficient, see below
An enum can have abstract methods, in that case, all instances have to implement the missing method bodies using the same syntax as the anonymous class one In that case, the compiler generates one anonymous class per enum instance.
interface FilePredicate {
boolean test(Path path) throws IOException;
}
enum FileListMode implements FilePredicate {
ALL {
public boolean test(Path path) throws IOException {
return true;
}
},
NORMAL {
public boolean test(Path path) throws IOException {
return !Files.isHidden(path);
}
},
DIRECTORY {
public boolean test(Path path) throws IOException {
return NORMAL.test(path) && Files.isDirectory(path);
}
}
}
It can be used to list the files of a directory in a way that depend on the mode. If you don't understand the cast in the for loop see chapter 'iteration'
void printAllPath(Path directory, FileListMode mode) throws IOException {
try(var stream = Files.list(directory)) {
for(var path: (Iterable<Path>)stream::iterator) {
if (mode.test(path)) {
System.out.println(path);
}
}
}
}
printAllPath(Path.of("."), FileListMode.DIRECTORY);
The implementation above uses inheritance where it should use delegation Here is a better implementation delegating each implementation to a lambda.
enum FileListMode {
ALL(path -> true),
NORMAL(path -> !Files.isHidden(path)),
DIRECTORY(path -> NORMAL.test(path) && Files.isDirectory(path))
;
private final FilePredicate predicate;
FileListMode(FilePredicate predicate) {
this.predicate = predicate;
}
public boolean test(Path path) throws IOException {
return predicate.test(path);
}
}
printAllPath(Path.of("."), FileListMode.DIRECTORY);
There are one implementations of set (respectively map) specific if all values comes from the same enum because in that case ordinal() is a perfect hash function
so a EnumSet is implemented
- using only one long if there is less than 64 enum instances
- using an array of longs if there are more instances because there are two implementations, you have to use factory methods that takes the enum class to get an instance of the set
var emptySet = EnumSet.noneOf(Modifier.class);
var enumSet = EnumSet.of(Modifier.PUBLIC, Modifier.FINAL);
System.out.println(enumSet);
and EnumMap is implemented as an array of values, the index being the value of ordinal()
var enumMap = new EnumMap<>(Map.of(Modifier.PUBLIC, "private", Modifier.FINAL, "final"));
System.out.println(enumMap);