Enum Tricks: Customized valueOf
When I am writing enumerations I very often found myself implementing a static method similar to the standard enum’s valueOf() but based on field rather than name:
public static TestOne valueOfDescription(String description) {
for (TestOne v : values()) {
if (v.description.equals(description)) {
return v;
}
}
throw new IllegalArgumentException(
"No enum const " + TestOne.class + "@description." + description);
}
Where “description” is yet another String field in my enum. And I am not alone. See this article for example.
Obviously this method is very ineffective. Every time it is invoked it
iterates over all members of the enum. Here is the improved version that
uses a cache:
private static Map map = null;
public static TestTwo valueOfDescription(String description) {
synchronized(TestTwo.class) {
if (map == null) {
map = new HashMap();
for (TestTwo v : values()) {
map.put(v.description, v);
}
}
}
TestTwo result = map.get(description);
if (result == null) {
throw new IllegalArgumentException(
"No enum const " + TestTwo.class + "@description." + description);
}
return result;
}
It is fine if we have only one enum and only one custom field that we use to find the enum value. But if we have 20 enums, and each has 3 such fields, then the code will be very verbose. As I dislike copy/paste programming I have implemented a utility that helps to create such methods. I called this utility class ValueOf. It has 2 public methods:
public static <T extends Enum<T>, V> T valueOf(Class<T> enumType, String fieldName, V value);
which finds the required field in specified enum. It is implemented utilizing reflection and uses a hash table initialized during the first call for better performance. The other overridden valueOf() looks like:
public static <T extends Enum<T>> T valueOf(Class<T> enumType, Comparable<T> comparable);
This method does not cache results, so it iterates over enum members on each invocation. But it is more universal: you can implement comparable as you want, so this method may find enum members using more complicated criteria.
Full code with examples and JUnit test case are available here.Conclusions
Java Enums provide the ability to locate enum members by name. This article describes a utility that makes it easy to locate enum members by any other field.(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)





Comments
Carlos Hoces replied on Sat, 2010/10/16 - 9:09am
I don't generally use valueOf in my enum classes. You may have a look at the following code, in case it may give you some ideas related to tricks around enum classes.
<code>
package org.jptools.tools.system.utils;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
public enum JavaVersion {
JDK1_0 {
@Override
boolean isJDK(String javaVersion) {
return javaVersion.indexOf("1.0.") >= 0;
}
@Override
public int getID() {
return 10;
}
},
JDK1_1 {
@Override
boolean isJDK(String javaVersion) {
return javaVersion.indexOf("1.1.") >= 0;
}
@Override
public int getID() {
return 11;
}
},
JDK1_2 {
@Override
boolean isJDK(String javaVersion) {
return javaVersion.indexOf("1.2.") >= 0;
}
@Override
public int getID() {
return 12;
}
},
JDK1_3 {
@Override
boolean isJDK(String javaVersion) {
return javaVersion.indexOf("1.3.") >= 0;
}
@Override
public int getID() {
return 13;
}
},
JDK1_4 {
@Override
boolean isJDK(String javaVersion) {
return javaVersion.indexOf("1.4.") >= 0;
}
@Override
public int getID() {
return 14;
}
},
JDK1_5 {
@Override
boolean isJDK(String javaVersion) {
return javaVersion.indexOf("1.5.") >= 0;
}
@Override
public int getID() {
return 15;
}
},
JDK1_6 {
@Override
boolean isJDK(String javaVersion) {
return javaVersion.indexOf("1.6.") >= 0 && !JDK1_6N.isJDK(javaVersion);
}
@Override
public int getID() {
return 16;
}
},
JDK1_6N {
@Override
boolean isJDK(String javaVersion) {
boolean result = false;
if (javaVersion.indexOf("1.6.") >= 0) {
for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel".equals(info.getClassName())) {
result = true;
break;
}
}
}
return result;
}
@Override
public int getID() {
return 1610;
}
},
JDK1_7 {
@Override
boolean isJDK(String javaVersion) {
return javaVersion.indexOf("1.7.") >= 0;
}
@Override
public int getID() {
return 17;
}
};
private static final String jdkVersionName;
private static final Map<Integer, JavaVersion> lookup;
static {
jdkVersionName = System.getProperty("java.version");
lookup = new HashMap<Integer, JavaVersion>();
for (JavaVersion versionNumber : EnumSet.allOf(JavaVersion.class)) {
lookup.put(versionNumber.getID(), versionNumber);
}
}
abstract boolean isJDK(String javaVersion);
public abstract int getID();
public static String getCurrentVersionName() {
return jdkVersionName;
}
public static int getJavaVersion(String javaVersion) {
int version = 13;
for (JavaVersion id : EnumSet.allOf(JavaVersion.class)) {
if (id.isJDK(javaVersion)) {
version = id.getID();
break;
}
}
return version;
}
public static JavaVersion getField(final Integer versionNumber) {
return lookup.get(versionNumber);
}
}
</code>
Alexander Radzin replied on Sat, 2010/10/16 - 10:40am
in response to:
Carlos Hoces
Carlos, thank you for your comment and code snippet. I think that your method getJavaVersion(String) is an example of "customized valueOf" in my terms. This method could be refactoried using my utility. In this case it will be one line long.
David Karr replied on Sat, 2010/10/16 - 12:31pm
Nicolas Labrot replied on Sat, 2010/10/16 - 2:21pm
TestOne and TestTwo can be improved :
With this improvment, TestOne is slower than TestTwo
But the test is always on the last values of the enum. With a simple modulo, TestOne time is equal to TestTwo time for only 3 values.
Alexander Radzin replied on Sat, 2010/10/16 - 5:09pm
in response to:
Nicolas Labrot
Alexander Radzin replied on Sat, 2010/10/16 - 5:13pm
in response to:
David Karr
Pieter van der Meer replied on Tue, 2010/10/19 - 2:26am
But why in gods name do you want such a code bloat! Don't you guys follow the KISS principle. The solution presented destroys this.
Within the enum is allowed to have multiple fields so the sample from carlos can be:
public enum JavaVersion { private String name; private int version; JavaVersion(name, version) { this.name = name; this.version = version) } JDK1_0("1,0",10) JDK1_2("1,2", 12) ..... String toString() { return name; } int getCode() { return this.code; }Plain and simple. And more important it does not implement logic into the enumeration. IMHO this is evil! Did't you learn the lesson about "separations of concerns"?
This may read like a flame it is not intention. I have seen a similar solution in production code and it was a nightmare to resolve. Functionality was everywhere and very hard to read. hence the KISS and SOC principles.
Carlos Hoces replied on Wed, 2010/10/20 - 6:55am
Alexander Radzin replied on Sun, 2010/10/24 - 5:24pm
in response to:
Pieter van der Meer
Pieter van der Meer replied on Thu, 2010/10/28 - 2:46am
reading your comment i might have jumped to conclusions. but still i think there is a better solution for this. most of what you are doing is described in detail by Joshua Bloch (Effective Java (2nd Edition) on page 147 and further).
And yes i think i miss something about what you attempt to achieve with the enumeration. Looking deeper into the class, my 2 cents is that you are mixing responsibilities. The method JDK1_6N.isJDK is doing something completely different than all other isJDK methods. All of a sudden you are testing for some swing stuff??
I think that belongs in an other (perhaps static) class that has "Swing" or "UI" or "LookAndFeel" in its class name.
By no means i tried to offend you, but was quick on the keyboard ;-)
Carlos Hoces replied on Sun, 2010/10/31 - 8:28pm
in response to:
Pieter van der Meer
Pieter, no offense at all! :-)
I took that piece of code to better explain there are different approaches, to solve the problem of getting an enum field out of its contents.
In fact, the real production code consists of two enums -the posted and another one- and a helper class as a public API. The problem is: when you ask the JVM for its version, it doesn`t return a simple "1.7" string, so there's a need for some logic to check out which one is. The problem with 1.6N is even worse.
The goal by using those enum is to keep the public API which does the actual check as clean and lean as possible, while hidding into those enum classes the bulk of detection logic.
enum classes are just a collection of singleton classes, disguised as "fields", so you may use them as any other Java class. The logic has to be somewhere: you may keep enum as simple as possible, but then you have to place the required logic somewhere else. That means helper classes, or move the logic to the public API class.
I doubt what you gain by keeping enum simple, won't be even worse by spreading the logic into other classes.
I tend to encapsulate logic as much as possible, in order to have public API simple to use, to maintain and to understand. It's an strategy as valid or as problematic as others. I use enum classes as I would do with singletons. Is it questionable?, maybe! But I don't see anything incorrect in that design methodology.
I also can be too quick on the keyboard ;-)