Alex is working as a software developer since 1997. He coded many languages and started coding Java in 2000. As a person that likes to share his experience with others he started his professional blog http://alexr.blog.com Alexander has posted 5 posts at DZone. View Full User Profile

Enum Tricks: Customized valueOf

10.16.2010
| 36766 views |
  • submit to reddit

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.
Published at DZone with permission of its author, Alexander Radzin.

(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

I really urge you to not assume that the map version is faster than the iterative lookup version.  If the number of values in an enum class are small, the iterative search may well be faster.  Measure it for different sizes.

Nicolas Labrot replied on Sat, 2010/10/16 - 2:21pm

TestOne and TestTwo can be improved :

  • TestOne : the synchronized is slow. The cache can be init on a static block or the synchronized can be inside the if null
  • TestTwo : the function values() slow down the valueOf. It could be better to cache it

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

I agree that the map may be initialized in static block. This is the difference between static and lazy initialization. I absolutely agree with bullet #2.

Alexander Radzin replied on Sat, 2010/10/16 - 5:13pm in response to: David Karr

OK, probably better implementation should check the number of enum elements and avoid using cache for small numbers. Although I do not think that it is so critical (IMHO).

Pieter van der Meer replied on Tue, 2010/10/19 - 2:26am

OK, i understand that in some rare case you really need the a String representation of an Enum.
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

Please, Pieter, think twice about the difference between simple and simplistic. You may have missed something while reading my code, and haven't took into account the implications over the remaining code using that enum class.

Alexander Radzin replied on Sun, 2010/10/24 - 5:24pm in response to: Pieter van der Meer

Pieter, thank you for reply. Your example does not contain method valueOf(int code) that returns JDK1_0 for code 10 and JDK1_2 for code 12. If you have to implement this you need some kind of lookup table. If you have 5 fields like code you need 5 lookup tables. If you are required to implement 10 enums like JavaVersion with 5 fields like code you need 50 lookup table. Every time you have to write similar code. My article is an attempt to reduce your efforts and save your time.

Pieter van der Meer replied on Thu, 2010/10/28 - 2:46am

Carlos,
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 ;-)

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.