Case-insensitive matching of a string to a Java enum

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.

86222 次浏览

You can use ABC0'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()) {

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

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"));
}

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");

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.

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;
}
}

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)

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())); }

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();
}
}

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

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