forked from forax/java-guide
-
Notifications
You must be signed in to change notification settings - Fork 0
/
chapter17-enum.jsh
165 lines (141 loc) · 5.96 KB
/
chapter17-enum.jsh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// To starts, run jshell --enable-preview which is a program able to interpret Java syntax
// then cut and paste the following lines to see how it works
// To exit jshell type /exit
// # Enum
// 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 }
// ## Enum instances
// 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 :)
// ## Enum are classes
// 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());
// ### Enum constructors
// 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
// ## Enum with abstract methods
// 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);
// ### Use delegation, not inheritance
// 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);
// ## EnumSet and EnumMap
// 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);