174
votes

I would like to lookup an enum from its string value (or possibly any other value). I've tried the following code but it doesn't allow static in initialisers. Is there a simple way?

public enum Verbosity {

    BRIEF, NORMAL, FULL;

    private static Map<String, Verbosity> stringMap = new HashMap<String, Verbosity>();

    private Verbosity() {
        stringMap.put(this.toString(), this);
    }

    public static Verbosity getVerbosity(String key) {
        return stringMap.get(key);
    }
};
12
IIRC, that gives an NPE because the static initialisation is done top down (i.e. the enum constants at the top are constructed before it gets down to the stringMap initialisation). The usual solution is to use a nested class.Tom Hawtin - tackline
Thank you everyone for such rapid response. (FWIW I didn't find the Sun Javadocs very useful for this problem).peter.murray.rust
It's really a language issue than a library issue. However, I think the API docs are read more than the JLS (although perhaps not by language designers), so things like this should probably have more prominence in the java.lang docs.Tom Hawtin - tackline

12 Answers

256
votes

Use the valueOf method which is automatically created for each Enum.

Verbosity.valueOf("BRIEF") == Verbosity.BRIEF

For arbitrary values start with:

public static Verbosity findByAbbr(String abbr){
    for(Verbosity v : values()){
        if( v.abbr().equals(abbr)){
            return v;
        }
    }
    return null;
}

Only move on later to Map implementation if your profiler tells you to.

I know it's iterating over all the values, but with only 3 enum values it's hardly worth any other effort, in fact unless you have a lot of values I wouldn't bother with a Map it'll be fast enough.

153
votes

You're close. For arbitrary values, try something like the following:

public enum Day { 

    MONDAY("M"), TUESDAY("T"), WEDNESDAY("W"),
    THURSDAY("R"), FRIDAY("F"), SATURDAY("Sa"), SUNDAY("Su"), ;

    private final String abbreviation;

    // Reverse-lookup map for getting a day from an abbreviation
    private static final Map<String, Day> lookup = new HashMap<String, Day>();

    static {
        for (Day d : Day.values()) {
            lookup.put(d.getAbbreviation(), d);
        }
    }

    private Day(String abbreviation) {
        this.abbreviation = abbreviation;
    }

    public String getAbbreviation() {
        return abbreviation;
    }

    public static Day get(String abbreviation) {
        return lookup.get(abbreviation);
    }
}
26
votes

with Java 8 you can achieve with this way:

public static Verbosity findByAbbr(final String abbr){
    return Arrays.stream(values()).filter(value -> value.abbr().equals(abbr)).findFirst().orElse(null);
}
12
votes

@Lyle's answer is rather dangerous and I have seen it not work particularly if you make the enum a static inner class. Instead I have used something like this which will load the BootstrapSingleton maps before the enums.

Edit this should not be a problem any more with modern JVMs (JVM 1.6 or greater) but I do think there are still issues with JRebel but I haven't had a chance to retest it.

Load me first:

   public final class BootstrapSingleton {

        // Reverse-lookup map for getting a day from an abbreviation
        public static final Map<String, Day> lookup = new HashMap<String, Day>();
   }

Now load it in the enum constructor:

   public enum Day { 
        MONDAY("M"), TUESDAY("T"), WEDNESDAY("W"),
        THURSDAY("R"), FRIDAY("F"), SATURDAY("Sa"), SUNDAY("Su"), ;

        private final String abbreviation;

        private Day(String abbreviation) {
            this.abbreviation = abbreviation;
            BootstrapSingleton.lookup.put(abbreviation, this);
        }

        public String getAbbreviation() {
            return abbreviation;
        }

        public static Day get(String abbreviation) {
            return lookup.get(abbreviation);
        }
    }

If you have an inner enum you can just define the Map above the enum definition and that (in theory) should get loaded before.

7
votes

And you can't use valueOf()?

Edit: Btw, there is nothing stopping you from using static { } in an enum.

5
votes

In case it helps others, the option I prefer, which is not listed here, uses Guava's Maps functionality:

public enum Vebosity {
    BRIEF("BRIEF"),
    NORMAL("NORMAL"),
    FULL("FULL");

    private String value;
    private Verbosity(final String value) {
        this.value = value;
    }

    public String getValue() {
        return this.value;
    }

    private static ImmutableMap<String, Verbosity> reverseLookup = 
            Maps.uniqueIndex(Arrays.asList(Verbosity.values()), Verbosity::getValue);

    public static Verbosity fromString(final String id) {
        return reverseLookup.getOrDefault(id, NORMAL);
    }
}

With the default you can use null, you can throw IllegalArgumentException or your fromString could return an Optional, whatever behavior you prefer.

3
votes

since java 8 you can initialize the map in a single line and without static block

private static Map<String, Verbosity> stringMap = Arrays.stream(values())
                 .collect(Collectors.toMap(Enum::toString, Function.identity()));
1
votes
public enum EnumRole {

ROLE_ANONYMOUS_USER_ROLE ("anonymous user role"),
ROLE_INTERNAL ("internal role");

private String roleName;

public String getRoleName() {
    return roleName;
}

EnumRole(String roleName) {
    this.roleName = roleName;
}

public static final EnumRole getByValue(String value){
    return Arrays.stream(EnumRole.values()).filter(enumRole -> enumRole.roleName.equals(value)).findFirst().orElse(ROLE_ANONYMOUS_USER_ROLE);
}

public static void main(String[] args) {
    System.out.println(getByValue("internal role").roleName);
}

}

0
votes

Perhaps, take a look at this. Its working for me. The purpose of this is to lookup 'RED' with '/red_color'. Declaring a static map and loading the enums into it only once would bring some performance benefits if the enums are many.

public class Mapper {

public enum Maps {

    COLOR_RED("/red_color", "RED");

    private final String code;
    private final String description;
    private static Map<String, String> mMap;

    private Maps(String code, String description) {
        this.code = code;
        this.description = description;
    }

    public String getCode() {
        return name();
    }

    public String getDescription() {
        return description;
    }

    public String getName() {
        return name();
    }

    public static String getColorName(String uri) {
        if (mMap == null) {
            initializeMapping();
        }
        if (mMap.containsKey(uri)) {
            return mMap.get(uri);
        }
        return null;
    }

    private static void initializeMapping() {
        mMap = new HashMap<String, String>();
        for (Maps s : Maps.values()) {
            mMap.put(s.code, s.description);
        }
    }
}
}

Please put in your opinons.

0
votes

If you want a default value and don't want to build lookup maps, you can create a static method to handle that. This example also handles lookups where the expected name would start with a number.

    public static final Verbosity lookup(String name) {
        return lookup(name, null);
    }

    public static final Verbosity lookup(String name, Verbosity dflt) {
        if (StringUtils.isBlank(name)) {
            return dflt;
        }
        if (name.matches("^\\d.*")) {
            name = "_"+name;
        }
        try {
            return Verbosity.valueOf(name);
        } catch (IllegalArgumentException e) {
            return dflt;
        }
    }

If you need it on a secondary value, you would just build the lookup map first like in some of the other answers.

-1
votes

You can define your Enum as following code :

public enum Verbosity 
{
   BRIEF, NORMAL, FULL, ACTION_NOT_VALID;
   private int value;

   public int getValue()
   {
     return this.value;
   } 

   public static final Verbosity getVerbosityByValue(int value)
   {
     for(Verbosity verbosity : Verbosity.values())
     {
        if(verbosity.getValue() == value)
            return verbosity ;
     }

     return ACTION_NOT_VALID;
   }

   @Override
   public String toString()
   {
      return ((Integer)this.getValue()).toString();
   }
};

See following link for more clarification

-2
votes

You can use the Enum::valueOf() function as suggested by Gareth Davis & Brad Mace above, but make sure you handle the IllegalArgumentException that would be thrown if the string used is not present in the enum.