0

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

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).

7
  • 3 items linear search is no big deal, but in real life uses there may be more.
    – CPlus
    May 20, 2023 at 21:43
  • @user16217248 read the rest. Still not going to be a problem in upwards of 99% of all cases. May 20, 2023 at 21:44
  • Fair enough then.
    – CPlus
    May 20, 2023 at 22:16
  • Initializing this.identifier inside the map.put() is clever. Too clever. May 21, 2023 at 1:05
  • 1
    I 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. May 29, 2023 at 20:23
0

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.

7
  • Then I could put the map in the interface.
    – CPlus
    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. 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.
    – CPlus
    May 20, 2023 at 22:31
  • Thank you about this information, It's very interesting. May 20, 2023 at 22:46
  • 1
    MyEnum.getMyEnumByCode(code) seems a tad redundant. May 21, 2023 at 0:50
0

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.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.