Can I get an enum based on the value of its field?
Add a static method to the CrimeCategory
enum:
public static CrimeCategory valueOf(String name) {
for (CrimeCategory category : values()) {
if (category.category.equals(name)) {
return category;
}
}
throw new IllegalArgumentException(name);
}
Assylias answer is great. Though I would return an Optional
from the factory method to let the client deal with the situation when enum could not be found (of course throwing IllegalArgumentException
might be better if you use this enum internally and you think that invoking this method with wrong argument will never happen - this is your choice).
And also I would wrap the Map
into unmodifiable wrapper to not modify it somewhere by accident inside your enum (the Map
is private but someone could modify it later when adding new functionalities - it will at least force to think about it) :
enum CrimeCategory {
ASBO("Anti Social Behaviour"),
BURG("Burglary"),
CRIMDAM("Criminal Damage And Arson"),
DRUGS("Drugs"),
OTHTHEFT("Other Theft"),
PUPDISOR("Public Disorder And Weapons"),
ROBBERY("Robbery"),
SHOPLIF("Shoplifting"),
VEHICLE("Vehicle Crime"),
VIOLENT("Violent Crime"),
OTHER("Other Crime");
private static final Map<String, CrimeCategory> MAP;
static {
Map<String, CrimeCategory> crimeCategoryMap = Arrays.stream(values())
.collect(toMap(cg -> cg.category, e -> e));
MAP = Collections.unmodifiableMap(crimeCategoryMap);
}
private final String category;
private CrimeCategory(String category) {
this.category = category;
}
public static Optional<CrimeCategory> of(final String name) {
return Optional.ofNullable(MAP.get(name));
}
}
For reference, here is an alternative solution with a HashMap:
enum CrimeCategory {
ASBO("Anti Social Behaviour"),
BURG("Burglary"),
CRIMDAM("Criminal Damage And Arson"),
DRUGS("Drugs"),
OTHTHEFT("Other Theft"),
PUPDISOR("Public Disorder And Weapons"),
ROBBERY("Robbery"),
SHOPLIF("Shoplifting"),
VEHICLE("Vehicle Crime"),
VIOLENT("Violent Crime"),
OTHER("Other Crime");
private static final Map<String, CrimeCategory> map = new HashMap<>(values().length, 1);
static {
for (CrimeCategory c : values()) map.put(c.category, c);
}
private final String category;
private CrimeCategory(String category) {
this.category = category;
}
public static CrimeCategory of(String name) {
CrimeCategory result = map.get(name);
if (result == null) {
throw new IllegalArgumentException("Invalid category name: " + name);
}
return result;
}
}
Static factory methods that return an enum constant based on the value of an instance field take on one of the two forms described in the other answers: a solution based on iterating the enum values, or a solution based on a HashMap
.
For enums with a small number of constants, the iterative solution should be as performant as the HashMap
solution (which requires calculation of the hash code, matching it to a bucket, and assuming that there will be no hash collisions).
For larger enums, the map-based solution will be more performant (but requires storage space in memory). However, if the factory method is invoked infrequently then the overall performance improvement by using a map could still be immeasurably small.
The overall decision to use an iterative lookup or a map-based lookup for the static factory method will ultimately depend on your requirements and the environment. It is never wrong to start with an iterative lookup and then change to a map-based implementation if profiling shows an actual performance problem.
Lastly, since Java 8, the Streams
API enables a pipeline-based solution for mapping (that should have performance similar to the iterative solution). For example, say that you want to create an interface that you could use on any enum class to express your intent that it should be matchable by one of its instance fields. Let's call this interface Matchable
. This interface defines a method which returns the instance field on which you want to match (eg. getField()
). This interface can also define a static factory method to return a constant from any implementing enum class:
interface Matchable {
Object getField();
public static <E extends Enum<E> & Matchable> E forToken(Class<E> cls, Object token) {
return Stream.of(cls.getEnumConstants())
.filter(e -> e.getField().equals(token))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown token '" +
token + "' for enum " + cls.getName()));
}
}
Now, any enum class that you define that implements Matchable
can use the Matchable.forToken()
static factory method to find the enum constant whose instance field value matches the supplied parameter.
The generic type declaration E extends Enum<E> & Matchable
assures that the type token passed to the method as a parameter will be for an enum class that implements Matchable
(otherwise the code won't compile).