59

Java provides a valueOf() method for every Enum<T> object, so given an enum like

public enum Day {
  Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
}

one can do a lookup like

Day day = Day.valueOf("Monday");

If the string passed to valueOf() does not match (case sensitive) an existing Day value, an IllegalArgumentException is thrown.

To do a case-insensitive matching, one can write a custom method inside the Day enum, e.g.

public static Day lookup(String day) {
  for (Day d : Day.values()) {
    if (d.name().equalsIgnoreCase(day)) {
      return type;
    }
  }
  return null;
}

Is there any generic way, without using caching of values or any other extra objects, to write a static lookup() method like the above only once (i.e., not for every enum), given that the values() method is implicitly added to the Enum<E> class at compile time?

The signature of such a "generic" lookup() method would be similar to the Enum.valueOf() method, i.e.:

public static <T extends Enum<T>> T lookup(Class<T> enumType, String name);

and it would implement exactly the functionality of the Day.lookup() method for any enum, without the need to re-write the same method for each enum.

PNS
  • 17,431
  • 26
  • 86
  • 131
  • 7
    I'm sure you know it but the reason why the issue ever appeared is that you don't stick to the [Java enum naming convention](https://stackoverflow.com/questions/3069743/coding-conventions-naming-enums). If you were using standard uppercase naming, you would just use `Day.valueOf(day.toUpperCase())` in your lookup method – kiedysktos May 23 '17 at 08:30
  • No, the same requirement is applicable irrespective of case or other naming conventions. A common method for all the purposes was the request. :-) – PNS May 25 '17 at 01:15
  • 1
    Adding to @kiedysktos suggestion (which, to anyone else reading, is by far the most straightforward solution), I suspect OP broken the naming convention because they want the toString form to have only the first letter uppercased as is grammatically correct English. You can still accomplish this by overriding the toString method: `return super.toString().substring(0, 1).toUpperCase() + super.toString().substring(1);` – Ataraxia Nov 14 '18 at 08:44

9 Answers9

48

I found getting the special blend of generics a little tricky, but this works.

public static <T extends Enum<?>> T searchEnum(Class<T> enumeration,
        String search) {
    for (T each : enumeration.getEnumConstants()) {
        if (each.name().compareToIgnoreCase(search) == 0) {
            return each;
        }
    }
    return null;
}

Example

public enum Horse {
    THREE_LEG_JOE, GLUE_FACTORY
};

public static void main(String[] args) {
    System.out.println(searchEnum(Horse.class, "Three_Leg_Joe"));
    System.out.println(searchEnum(Day.class, "ThUrSdAy"));
}
Adam
  • 32,907
  • 8
  • 89
  • 126
  • 4
    Yes, this works. Thanks for the pointer to the `getEnumConstants()` method. I was actually trying to avoid calling the `values()` method via reflection, which is what the `getEnumConstants()` actually does, but apparently there is no better approach. By the way, you can just use `equalsIgnoreCase()` for the string comparison. – PNS Feb 04 '15 at 23:56
  • Would it be worthwhile to cache the values into a map once rather than iterating them every time? e.g. `Map` where the value is already lowercased or something. – jocull Apr 25 '19 at 20:14
  • Reflection is fast enough but caching is faster, so yes, it would make sense for specific enums, or if the method would be called only for a few enums. In the use case described in the question caching is not applicable, since the `lookup` method is completely generic. – PNS Mar 05 '21 at 16:50
37

I would think the easiest safe way to do it would be:

Arrays.stream(Day.values())
    .filter(e -> e.name().equalsIgnoreCase(dayName)).findAny().orElse(null);

Or if you want to use the class object, then:

Arrays.stream(enumClass.getEnumConstants())
    .filter(e -> (Enum)e.name().equalsIgnoreCase(dayName)).findAny().orElse(null);
sprinter
  • 24,103
  • 5
  • 40
  • 71
  • Hmmm... The `values()` method is not available for the `enumType` parameter, which is of type `Class`. – PNS Feb 05 '15 at 00:04
  • @PNS I had thought that was the enum name. I've changed to use the enum in the example. – sprinter Feb 05 '15 at 01:48
14

Starting from version 3.8 apache commons-lang EnumUtils has two handy methods for this:

  • getEnumIgnoreCase(final Class<E> enumClass, final String enumName)
  • isValidEnumIgnoreCase(final Class<E> enumClass, final String enumName)
Enigo
  • 3,093
  • 5
  • 26
  • 48
6

For Android and relatively short Enums, I do the simple loop and compare the name ignoring the case.

public enum TransactionStatuses {
    public static TransactionStatuses from(String name) {
        for (TransactionStatuses status : TransactionStatuses.values()) {
            if (status.name().equalsIgnoreCase(name)) {
                return status;
            }
        }
        return null;
    }
}
Alen Siljak
  • 2,147
  • 1
  • 21
  • 26
5

A generic solution would be to keeo to the convention that constants are uppercase. (Or in your specific case use a capitalize on the look-up string).

public static <E extends Enum<E>> E lookup(Class<E> enumClass,
        String value) {
    String canonicalValue.toUpperCase().replace(' ', '_');
    return Enum<E>.valueOf(enumClass, canonicalValue);
}

enum Day(MONDAY, ...);
Day d = lookup(Day,class, "thursday");
Joop Eggen
  • 96,344
  • 7
  • 73
  • 121
  • Could be, but this is not always possible, because we don't always have control on who writes the enum. :-) – PNS Feb 04 '15 at 23:50
4

You can use Class's getEnumConstants() method, which returns an array of all the enum types, if the Class represents an enum, or null if not.

Returns the elements of this enum class or null if this Class object does not represent an enum type.

Your enhanced for loop line would look like this:

for (T d : enumType.getEnumConstants()) {
rgettman
  • 167,281
  • 27
  • 248
  • 326
  • Yes, that is a good solution. I should have looked in the Class class, in addition to Enum. Thanks. – PNS Feb 04 '15 at 23:49
1

and it would implement exactly the functionality of the Day.lookup() method for any enum, without the need to re-write the same method for each enum.

Probably you can write a utility class for doing that as the following.

public class EnumUtil {

    private EnumUtil(){
        //A utility class
    }

    public static <T extends Enum<?>> T lookup(Class<T> enumType,
                                                   String name) {
        for (T enumn : enumType.getEnumConstants()) {
            if (enumn.name().equalsIgnoreCase(name)) {
                return enumn;
            }
        }
        return null;
    }

    // Just for testing
    public static void main(String[] args) {
        System.out.println(EnumUtil.lookup(Day.class, "friday"));
        System.out.println(EnumUtil.lookup(Day.class, "FrIdAy"));
    }

}

enum Day {
    Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
}

It would have been nice if there was a way in Java for us to extend the Enum class by implicitly adding methods just the way values() method is added but I don't think there is a way to do that.

Nandana
  • 1,190
  • 7
  • 17
1

I am using this way for case-insensitive matching of a string to a java enum Day[] days = Day.values(); for(Day day: days) { System.out.println("MONDAY".equalsIgnoreCase(day.name())); }

Atul Jain
  • 653
  • 2
  • 7
  • 18
1

I haven't tested this yet but why not overloading these methods as mentioned in this SO answer

public enum Regular {
    NONE,
    HOURLY,
    DAILY,
    WEEKLY;

    public String getName() {
        return this.name().toLowerCase();
    }    
}
Ahmed Hamed
  • 331
  • 3
  • 7