I can write an enum
with properties besides the name and ordinal. A very generic example would be:
public enum ExampleEnum {
EXAMPLE0(Example.example0),
EXAMPLE1(Example.example1),
EXAMPLE2(Example.example2);
public final Example identifier;
private ExampleEnum(final Example identifier) {
this.identifier = identifier;
}
}
In this case where Example
is an arbitrary user-defined class with several pre-existing instances available as static
fields. Every ExampleEnum
instance has an associated, unique Example
instance, but not the other way around. But what if I wanted to get an ExampleEnum
(or null
if there is no associated value) from a given Example
instance? There are a few ways to do this:
public ExampleEnum get(Example identifier) {
return
identifier == Example.example0 ? EXAMPLE0 :
identifier == Example.example1 ? EXAMPLE1 :
identifier == Example.example2 ? EXAMPLE2 :
null
;
}
This is terrible. A very long chained conditional statement that needs to be modified by hand if a new ExampleEnum
instance is added at a later point. Also a slow linear search.
A better option is a hash table:
private Map<Example, ExampleEnum> map = new HashMap<Example, ExampleEnum>();
private ExampleEnum(final Example identifier) {
map.put(this.identifier = identifier, this);
}
public ExampleEnum get(final Example identifier) {
return map.get(identifier);
}
The issue with this is a rule in Java that you cannot access static
fields in an enum
from the constructor. To get around this, it just so happened that in my use case I had a static enum
inside another class. I just placed the private static
map
field in the class instead of the enum
.
However, this seems a hack to fix a problem that is not applicable to all cases, for example, if the enum
is not inside another class. Alternatively I could create a single class whose sole purpose is to store these map
objects but that is not modular, as the enum
and the related map(s) are no longer self-contained.
How could I handle reverse enum
queries as described, in a way that respects modularity (does not place the map
in unrelated classes just to circumvent the enum
restriction) and that is graceful (not just a terrible if else
or ? :
chain)?
3 Answers 3
Option 1 is to use a private static nested class to hold the map:
public enum ExampleEnum {
EXAMPLE0(Example.example0),
EXAMPLE1(Example.example1),
EXAMPLE2(Example.example2);
public final Example identifier;
private static class MapHolder{
private static Map<Example, ExampleEnum> map = new HashMap<>();
}
private ExampleEnum(final Example identifier) {
MapHolder.map.put(this.identifier = identifier, this);
}
public ExampleEnum get(final Example identifier) {
return MapHolder.map.get(identifier);
}
}
Of course, this will compile under the hood to a syntetic class ExampleEnum$MapHolder
, but in the source code it's "modular".
Option 2 is to iterate over values()
:
public ExampleEnum get(final Example identifier) {
for(ExampleEnum e : values()){
if(e.identifier == identifier){
return e;
}
}
throw new IllegalArgumentException(); // or return null if you want
}
Personally, I would prefer this, as it introduces no new entities purely as a workaround. As for "slow linear search", that is very much a premature optimization.
First, a "linear search" over 3 elements is actually going to be faster than using a HashMap
. At what number it becomes slower would have to be tested; I'd actually bet money that it's higher than the average number of elements in a Java enum.
And even then, it's still a premature optimization. Not gonna matter unless that method gets called millions of times a second in a tight loop, which it very likely won't.
Oh, here's Option 3 for our lovers of functional programming:
public ExampleEnum get(final Example identifier) {
return Arrays.stream(values())
.filter(e->e.identifier==identifier)
.findFirst()
.orElseThrow();
}
IMO a wash with Option 2 in terms of code prettiness, probably a bit slower as well (still likely irrelevant).
-
3 items linear search is no big deal, but in real life uses there may be more.2023年05月20日 21:43:27 +00:00Commented May 20, 2023 at 21:43
-
@user16217248 read the rest. Still not going to be a problem in upwards of 99% of all cases.Michael Borgwardt– Michael Borgwardt2023年05月20日 21:44:57 +00:00Commented May 20, 2023 at 21:44
-
-
Initializing
this.identifier
inside themap.put()
is clever. Too clever.candied_orange– candied_orange2023年05月21日 01:05:27 +00:00Commented May 21, 2023 at 1:05 -
1I will go further and in fact claim that using a
HashMap
will be slower in 100% of all use cases for this enum. An enum that contains state has an upper limit on how many elements it can hold. Long story short, the max limit is less than 3 thousand values. I am pretty comfortable saying that using a HashTable/HashMap will not help for anything.davidalayachew– davidalayachew2023年05月29日 20:23:52 +00:00Commented May 29, 2023 at 20:23
For me I think the best way to create an enum
in Java and get its value is by using interface
and make your enum
implement this interface, after that you can create your methods inside the enum
. I will show you an example:
public interface InterfaceForEnumType {
String getCode();
String getMessage();
}
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum MyEnum implements IntarfaceForEnumType {
EXAMPLE1("example_1", "message_1"),
EXAMPLE2("example_2", "message_2"),
private String code;
private String message;
public static MyEnum getMyEnumByCode(final String code) {
for (final myEnum e : MyEnum.values()){
if (StringUtils.equals(e.getCode(), code)) {
return e;
}
}
return null;
}
}
I hope this will help.
-
Then I could put the map in the
interface
.2023年05月20日 22:20:01 +00:00Commented May 20, 2023 at 22:20 -
Actually you don't need to use the map anymore, because you have getMyEnumByCode method who can give you the value by using the code (id of enum value) as parameter.Issam El Orf– Issam El Orf2023年05月20日 22:25:38 +00:00Commented May 20, 2023 at 22:25
-
This code uses a linear search. Works unless number of elements is too big and performance is tight. But if I decide to use a map instead at a later point, I could put the map in the
interface
.2023年05月20日 22:31:52 +00:00Commented May 20, 2023 at 22:31 -
Thank you about this information, It's very interesting.Issam El Orf– Issam El Orf2023年05月20日 22:46:23 +00:00Commented May 20, 2023 at 22:46
-
1
MyEnum.getMyEnumByCode(code)
seems a tad redundant.candied_orange– candied_orange2023年05月21日 00:50:32 +00:00Commented May 21, 2023 at 0:50
One option is to loop though all the enum
values to initialize the map via static {}
block after all the values have been initialized. Although this is less efficient at initialization, this would only incur a one time cost.
public enum ExampleEnum {
EXAMPLE0(Example.example0),
EXAMPLE1(Example.example1),
EXAMPLE2(Example.example2);
public static final Map<Example, ExampleEnum> map;
static {
final ExampleEnum[] values = values();
final Map<Example, ExampleEnum> m =
new HashMap<Example, ExampleEnum>(values.length);
for (final ExampleEnum example : values)
m.put(example.identifier, example);
map = Collections.unmodifiableMap(m);
}
public final Example identifier;
private ExampleEnum(final Example identifier) {
this.identifier = identifier;
}
}
Another functional approach uses collection methods.
public static final Map<Example, ExampleEnum> map =
Collections.unmodifiableMap(Stream.of(values())
.collect(Collectors.toMap(e -> e.identifier, e -> e)));
The first option is probably faster but the latter option is shorter.