83

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
  • 19,295
  • 32
  • 96
  • 143
  • 11
    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
  • 1
    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);` – rurouniwallace Nov 14 '18 at 08:44

11 Answers11

65

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
  • 35,919
  • 9
  • 100
  • 137
  • 6
    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
46

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
  • 27,148
  • 6
  • 47
  • 78
  • 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
23

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,685
  • 5
  • 29
  • 54
7

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,482
  • 2
  • 24
  • 29
7

I can believe this, or a similar solution hasn't been posted yet. My preferred go-to here (there is absolutely no need for a 'lookup', just a smarter valueOf. Plus, as a bonus, enum values are all uppercase, as us former c++'ers think they should be...

public enum Day {
    MONDAY("Monday"), 
    TUESDAY("Tuesday"), 
    WEDNESDAY("Wednesday"),
    THURSDAY("Thursday"), 
    FRIDAY("Friday"),
    SATURDAY("Saturday"),
    SUNDAY("Sunday"); 

    public static Day valueOfIgnoreCase(String name) {
         return valueOf(name.toUpperCase());
    }

    private final String displayName; 

    Day(String displayName) {
        this.displayName = displayName;
    }

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

And then:

Day day = Day.valueOfIgnoreCase("mOnDay"); 
System.out.println(day); 

>>> Monday
gbtimmon
  • 4,238
  • 1
  • 21
  • 36
6

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
  • 107,315
  • 7
  • 83
  • 138
  • 1
    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
  • 176,041
  • 30
  • 275
  • 357
  • 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
2

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,240
  • 8
  • 17
2

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
  • 411
  • 4
  • 9
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
  • 1,035
  • 2
  • 16
  • 24
0

There are several different ways to approach solving the problem of finding a specific enum instances by name while ignoring case sensitivity. The solutions are listed in order of simplicity.

  1. An immediate local solution iterating over the name space (inefficient)
  2. A generic global solution iterating over the name space (inefficient)
  3. A pattern to cache enhance a specific enum (efficient)
  4. A generic pattern to cache all enums (efficient)

Given the following enum definition...

public enum EnumExample {
  AUTH_SLOTH,
  FUNKY_MONKEY,
  WIMP_CHIMP;
}

Solution 1: Inspired by this solution, it has been reified into a pair of public static methods to encourage DRY (Don't Repeat Yourself, i.e. copy pasta code duplication). To meet a one-off need, there's nothing stopping its being used directly, forgoing the formality of creating methods.

public static EnumExample valueOfIgnoreCaseDefaultToFirstListed(String name)
  return valueOfIgnoreCase(name, EnumExample.values()[0]);

public static EnumExample valueOfIgnoreCase(String name, EnumExample orElseDefault) {
  return Arrays.stream(EnumExample.values())
      .filter(enumExample -> enumExample.name().equalsIgnoreCase(name))
      .findAny()
      .orElse(orElseDefault);

System.out.println(valueOfIgnoreCaseDefaultToFirstListed("funky_MONkey"));
  //Found and prints FUNKY_MONKEY

System.out.println(valueOfIgnoreCaseDefaultToFirstListed("aUtH_sloth"));
  //Found and prints AUTH_SLOTH

System.out.println(valueOfIgnoreCaseDefaultToFirstListed("grape_APE"));
  //Not found, and prints default AUTH_SLOTH (which is the first listed)

System.out.println(valueOfIgnoreCase("funky_MONkey", EnumExample.WIMP_CHIMP));
  //Found and prints FUNKY_MONKEY

System.out.println(valueOfIgnoreCase("grape_APE", EnumExample.WIMP_CHIMP));
  //Not found and prints WIMP_CHIMP

System.out.println(valueOfIgnoreCase("grape_APE", null));
  //Not found and prints null

Solution 2: Inspired by this solution, it has also been reified into a pair of generic public static methods to encourage DRY. It is also recommended this be placed in a catch-all utilities class, if the intention is to use it globally across one's project(s).

public static <T extends Enum<?>> T valueOfIgnoreCaseDefaultToFirstListed(Class<T> classEnumT,
    String name)
  return valueOfIgnoreCase(classEnumT, name, classEnumT.getEnumConstants()[0]);

public static <T extends Enum<?>> T valueOfIgnoreCase(Class<T> classEnumT,
    String name, T orElseDefault)
  return Arrays.stream(classEnumT.getEnumConstants())
      .filter(enumTConstant -> enumTConstant.name().equalsIgnoreCase(name))
      .findAny()
      .orElse(orElseDefault);

System.out.println(valueOfIgnoreCaseDefaultToFirstListed(EnumExample.class, "funky_MONkey"));
//Found and prints FUNKY_MONKEY

System.out.println(valueOfIgnoreCaseDefaultToFirstListed(EnumExample.class, "aUtH_sloth"));
//Found and prints AUTH_SLOTH

System.out.println(valueOfIgnoreCaseDefaultToFirstListed(EnumExample.class, "grape_APE"));
//Not found, and prints default AUTH_SLOTH (which is the first listed)

System.out.println(valueOfIgnoreCase(EnumExample.class, "funky_MONkey", EnumExample.WIMP_CHIMP));
//Found and prints FUNKY_MONKEY

System.out.println(valueOfIgnoreCase(EnumExample.class, "grape_APE", EnumExample.WIMP_CHIMP));
//Not found and prints WIMP_CHIMP

System.out.println(valueOfIgnoreCase(EnumExample.class, "grape_APE", null));
//Not found and prints null

Solution 3: If the function is going to be used at scale, then it is worth considering investing the effort into implementing a caching pattern directly within the enum itself.

public enum EnumExample {
  AUTH_SLOTH,
  FUNKY_MONKEY,
  WIMP_CHIMP;

  final private static Map<String, EnumExample> BY_NAME_LOWER_CASE =
    Arrays.stream(EnumExample.values())
        .collect(
            Collectors
                .toMap(enumExample -> enumExample.name().toLowerCase(), Function.identity()));

  public static EnumExample valueOfIgnoreCaseDefaultToFirstListed(String name)
    return valueOfIgnoreCase(name, EnumExample.values()[0]);

  public static EnumExample valueOfIgnoreCase(String name, EnumExample orElseDefault)
    var result = BY_NAME_LOWER_CASE.get(name.toLowerCase());
    return (result != null)
        ? result
        : orElseDefault;
}

System.out.println(EnumExample.valueOfIgnoreCaseDefaultToFirstListed("funky_MONkey"));
//Found and prints FUNKY_MONKEY

System.out.println(EnumExample.valueOfIgnoreCaseDefaultToFirstListed("aUtH_sloth"));
//Found and prints AUTH_SLOTH

System.out.println(EnumExample.valueOfIgnoreCaseDefaultToFirstListed("grape_APE"));
//Not found, and prints default AUTH_SLOTH (which is the first listed)

System.out.println(EnumExample.valueOfIgnoreCase("funky_MONkey", EnumExample.WIMP_CHIMP));
//Found and prints FUNKY_MONKEY

System.out.println(EnumExample.valueOfIgnoreCase("grape_APE", EnumExample.WIMP_CHIMP));
//Not found and prints WIMP_CHIMP

System.out.println(EnumExample.valueOfIgnoreCase("grape_APE", null));
//Not found and prints null

Solution 4: If one is working with a larger quantity of enums across a larger code base, the pattern described in Solution 3 will likely result in significant boilerplate.

By combining the basic generic pattern from Solution 2 with the creation of a single global place to lazily cache the entire code base's enums, DRY is maximized while the boilerplate (anti-DRY) of Solution 3 has been significantly reduced.

private static final Map<Class<?>, Map<String, ?>> ENUM_INSTANCE_BY_NAME_LOWERCASE_BY_ENUMCLASS = new HashMap<>();

private static <T extends Enum<T>> Map<String, T> fetchCachedEnumInstanceByNameLowerCase(Class<T> classEnumT) {
  Map<String, T> result = null;

  var enumInstanceByNameLowerCase = ENUM_INSTANCE_BY_NAME_LOWERCASE_BY_ENUMCLASS.get(classEnumT);
  if (enumInstanceByNameLowerCase != null) {
    //noinspection unchecked
    result = (Map<String, T>)enumInstanceByNameLowerCase;
  } else {
    var tsByNameLowerCase =
        Arrays.stream(classEnumT.getEnumConstants())
            .collect(
                Collectors
                    .toMap(enumTConstant -> enumTConstant.name().toLowerCase(), Function.identity()));

    ENUM_INSTANCE_BY_NAME_LOWERCASE_BY_ENUMCLASS.put(classEnumT, tsByNameLowerCase);
    result = tsByNameLowerCase;
  }

  return result;
}

public static <T extends Enum<T>> T ValueOfIgnoreCaseDefaultToFirstListed(Class<T> classEnumT,
    String search)
  return (classEnumT != null)
      ? valueOfIgnoreCase(classEnumT, search, classEnumT.getEnumConstants()[0])
      : null;

public static <T extends Enum<T>> T valueOfIgnoreCase(Class<T> classEnumT,
    String search, T orElseDefault)
  return (classEnumT != null)
      ? fetchCachedEnumInstanceByNameLowerCase(classEnumT)
          .getOrDefault(search.toLowerCase(), orElseDefault)
      : null;

System.out.println(staticGenericCachedValueOfIgnoreCaseDefaultToFirstListed(EnumExample.class, "funky_MONkey"));
//Found and prints FUNKY_MONKEY

System.out.println(staticGenericCachedValueOfIgnoreCaseDefaultToFirstListed(EnumExample.class, "aUtH_sloth"));
//Found and prints AUTH_SLOTH

System.out.println(staticGenericCachedValueOfIgnoreCaseDefaultToFirstListed(EnumExample.class, "grape_APE"));
//Not found, and prints default AUTH_SLOTH (which is the first listed)

System.out.println(staticGenericCachedValueOfIgnoreCase(EnumExample.class, "funky_MONkey", EnumExample.WIMP_CHIMP));
//Found and prints FUNKY_MONKEY

System.out.println(staticGenericCachedValueOfIgnoreCase(EnumExample.class, "grape_APE", EnumExample.WIMP_CHIMP));
//Not found and prints WIMP_CHIMP

System.out.println(staticGenericCachedValueOfIgnoreCase(EnumExample.class, "grape_APE", null));
//Not found and prints null
chaotic3quilibrium
  • 5,661
  • 8
  • 53
  • 86