Use generic enum classes in maps - java

I have a map of class names to their enum class and I have method that parses a string like "SomeEnum.FIRST" into the actual object. But Enum.valueOf doesn't accept Class<? extends Enum<?>> while the map cannot store Class<T extends Enum<T>>.
For the code, the map looks something like this:
private static final HashMap<String, Class<? extends Enum<?>>> enumsMap;
static {
enumsMap = new HashMap<>();
// These are two DIFFERENT enum classes!
registerEnum(SomeEnum.class);
registerEnum(AnotherEnum.class);
}
private static void registerEnum(Class<? extends Enum<?>> enumClass) {
enumsMap.put(enumClass.getSimpleName(), enumClass);
}
And here is the parser (removed unnecessary code):
public <T extends Enum<T>> Object[] parse(List<String> strParameters) {
Object[] parameters = new Object[strParameters.size()];
for (int i = 0; i < parameters.length; i++) {
String strParameter = strParameters.get(i);
int delim = strParameter.lastIndexOf('.');
String className = strParameter.substring(0, delim - 1);
String enumName = strParameter.substring(delim + 1);
Class<T> enumClass = (Class<T>) enumsMap.get(className);
parameters[i] = Enum.valueOf(enumClass, enumName);
}
return parameters;
}
And now if I call this parse, my IDE (Android Studio) tells me, that "Unchecked method 'parse(List)' invocation", and afaik this is because of that generic type. If I remove it in parse, it wouldn't compile but the warning disappears. Is there a good way around it?

If you have enums like:
enum Foo {
A, B, C
}
enum Bar {
D, E, F
}
Then you can implement the kind of map you're talking about with the following code.
class MyEnums {
private final Map<String, Class<? extends Enum<?>>> map = new HashMap<>();
public void addEnum(Class<? extends Enum<?>> e) {
map.put(e.getSimpleName(), e);
}
private <T extends Enum<T>> T parseUnsafely(String name) {
final int split = name.lastIndexOf(".");
final String enumName = name.substring(0, split);
final String memberName = name.substring(split + 1);
#SuppressWarnings("unchecked")
Class<T> enumType = (Class<T>) map.get(enumName);
return Enum.valueOf(enumType, memberName);
}
public Object parse(String name) {
return parseUnsafely(name);
}
public Object[] parseAll(String... names) {
return Stream.of(names)
.map(this::parse)
.collect(toList())
.toArray();
}
}
This does not get around an unchecked cast, though; it only hides it from you temporarily. You can see where where SuppressWarnings is used to muffle the warning about enumType. It's generally good practice to apply the warning suppression in as limited a scope as possible. In this case, it's for that single assignment. While this could be a red flag in general, in the present case we know that the only values in the map are, in fact, enum classes, since they must have been added by addEnum.
Then, it can be used as:
MyEnums me = new MyEnums();
me.addEnum(Foo.class);
me.addEnum(Bar.class);
System.out.println(me.parse("Foo.A"));
System.out.println(me.parse("Bar.E"));
System.out.println(Arrays.toString(me.parseAll("Foo.B", "Bar.D", "Foo.C")));
which prints:
A
E
[B, D, C]
You'll notice that I broke parseUnsafely and parse into separate methods. The reason that we don't want to expose parseUnsafely directly is that it makes a guarantee by its return type that we cannot actually enforce. If it were exposed, then we could write code like
Bar bar = me.parseUnsafely("Foo.B");
which compiles, but fails at runtime with a cast class exception.

There is no safe way to have Map values whose generic type depends on the corresponding key.
You can, however, store the enum constants yourself:
private static final Map<String, Map<String, ?>> enumsMap;
static {
enumsMap = new HashMap<>();
// These are two DIFFERENT enum classes!
registerEnum(SomeEnum.class);
registerEnum(AnotherEnum.class);
}
private static <T extends Enum<T>> void registerEnum(Class<T> enumClass) {
Map<String, ?> valuesByName =
EnumSet.allOf(enumClass).stream().collect(
Collectors.toMap(Enum::name, Function.identity()));
enumsMap.put(enumClass.getSimpleName(), valuesByName);
}
public Object[] parse(List<String> strParameters) {
Object[] parameters = new Object[strParameters.size()];
for (int i = 0; i < parameters.length; i++) {
String strParameter = strParameters.get(i);
int delim = strParameter.lastIndexOf('.');
String className = strParameter.substring(0, delim);
String enumName = strParameter.substring(delim + 1);
Map<String, ?> enumValues = enumsMap.get(className);
parameters[i] = enumValues.get(enumName);
if (parameters[i] == null) {
throw new IllegalArgumentException("Class " + className
+ " does not contain constant " + enumName);
}
}
return parameters;
}
What I’ve changed:
enumsMap is now Map<String, Map<String, ?>>. Each value is a Map of enum constants keyed by constant name. ? is sufficient; there is no benefit to remembering that the constant values are enums, since parse returns Object[].
registerEnum has a generic type, to guarantee its argument is a valid enum type. Instead of storing the class argument, it stores that enum’s constants.
parse doesn’t need a generic type, since it returns Object[].
parse does not use any methods of Enum, so generic type safety is no longer a concern.
I fixed a bug: strParameter.substring(0, delim); instead of delim - 1. You want the entire substring up to but not including the period.

Related

Accept any enum as parameter

public static <E extends Enum<E>> List<E> enumHeadAsList(final Class<E> val, final E topValue)
{
// My code
}
I want to make a method that accept any enum i give and i set a default value of that enum to do some order check and then return all values as list. Basically i want set head of an enum and return values. So far i managed to do this but i don't know how to continue.
I want call the following method like:
enumHeadAsList(BuffType.class, BuffType.SELF);
This should do the trick:
public static <E extends Enum<E>> List<E> enumHeadAsList(final E topValue)
{
E[] values = topValue.getDeclaringClass().getEnumConstants();
if (topValue.ordinal() != 0) {
E oldTop = values[0];
values[0] = topValue;
values[topValue.ordinal()] = oldTop;
}
return List.of(values);
}
This code assumes that you don't care too much about the order of the remaining elements (as the top element is simply swapped with the previously first one).
If you want to keep the relative order of the other values intact, then you'll have to tweak replace the if above with this code block:
final int topValueIndex = topValue.ordinal();
if (topValueIndex != 0) {
System.arraycopy(values, 0, values, 1, topValueIndex);
values[0] = topValue;
}
Note that a dedicated Class<E> parameter is not needed if topValue is not allowed to be null, because we can get the enum class from topValue.
If using a Java version before Java 9, then you can replace List.of() with Arrays.asList() with the difference that the resulting List would be mutable.
EnumSet makes this easy:
public static <E extends Enum<E>> List<E> enumHeadAsList(final Class<E> val, final E topValue)
{
List<E> values = new ArrayList<>();
values.add(topValue);
values.addAll(EnumSet.complementOf(EnumSet.of(topValue)));
return values;
}
complementOf returns a collection of all enum values except those present in the argument. So, complementOf(EnumSet.of(topValue)) is all values except topValue.

Dynamically choose list type to iterate over a common for loop

I have a for loop which applies a Function for each element of a list. The list could be of type String or Long. Depending on a condition, I need to either iterate the String list or the Long list. Is there a way where I can do it without using if - else?
public static <T, S> T weirdFunction(Function<S, T> myFunction, Boolean convertToLong){
List<String> stringList = fetchFromSomewhere();
if(convertToLong){
// fetch list of longs from elsewhere
}
for (<String or Long> id : List<String> or List<Long>) { // choose list type based on some method param
myFunction.apply(id); // myFunction is of type Function
}
}
Having a function that returns another function, to be applied "recursively"1, makes this tricky.
Here is how it can be done:
#FunctionalInterface
interface ChainFunction<T> extends Function<T, ChainFunction<T>> {
// Nothing to add
}
static <T> ChainFunction<T> applyFunctionChain(List<T> list, ChainFunction<T> myFunction) {
for (T id : list) {
myFunction = myFunction.apply(id);
}
return myFunction;
}
The method can be called with String and with Long, and any other type for that matter.
1) "Recursive" is not the right word, but I don't know what it's called.
This presents one approach to call a different function per type without using an if statement. It requires one to build a map of different classes the list contains, each with a particular function. The containing class that houses the methods is SpecialList There is a caveat in this. Since the return value is cast to its actual type it can't be assigned in the loop unless it is returned as an Object. If the return type is not needed, then the return type could be set to void and the FunctionalInterface in the map could be a Consumer
Map<Class<?>, Function<Object, Object>> map = new HashMap<>();
map.put(String.class, SpecialList::stringFnc);
map.put(Long.class, SpecialList::longFnc);
List<Object> list = List.of("String1", 123L, "String2", 1234L,
29292L, "String4");
for (Object ob : list) {
map.get(ob.getClass()).apply(ob);
}
public static String stringFnc(Object str) {
String v = (String)str;
// now do something with v.
System.out.println("String : " + v);
return v;
}
public static Long longFnc(Object lng) {
long v = (Long)lng;
// now do something with v
System.out.println("Long : " + v);
return v;
}
prints
String : String1
Long : 123
String : String2
Long : 1234
Long : 29292
String : String4

How to access an unknown enum's "valueOf()" method

I have configuration files which can be populated with enums and their respective values and will then be read by my program.
For example, a configuration file (yaml format) may look like this:
SomeEnumClass:
- VALUE_A_OF_SOME_ENUM
- VALUE_B_OF_SOME_ENUM
- ANOTHER_VALUE
AnotherEnumClass:
- VALUE_1
- VALUE_3
- VALUE_3
- VALUE_7
[etc...]
Unfortunately this leads to duplication in my code (java) like this:
if (enumNameString.equals("SomeEnumClass")) {
Collection<SomeEnumClass> values = new ArrayList<>;
for (String listEntry : yamlConfig.getStringList(enumNameString)) {
values.add(SomeEnumClass.valueOf(listEntry));
}
return values;
} else if (enumNameString.equals("AnotherEnumClass")) {
Collection<AnotherEnumClass> values = new ArrayList<>;
for (String listEntry : yamlConfig.getStringList(enumNameString)) {
values.add(AnotherEnumClass.valueOf(listEntry));
}
return values;
} else if ...
} else if ...
} else if ...
(please keep in mind that this example is pseudo code)
So of course i'm trying to get rid of the duplicate code. But how?
Is it possible to:
Get a class from a string? ("SomeEnumClass" -> SomeEnumClass.class)
Then check if that class is castable to Enum or something?
Access the enum's valueOf() method from that cast?
As usual, all things reflection are typically evil. However to get all the enum constants for a fully named class:
Class.forName(enumNameString).getEnumConstants()
<T extends Enum<T> Enum.valueOf(Class<T>,String) is great, but I don't know of a reasonable, obviously safe way to narrow a Class<?> to a Class<T extends Enum<T>> (Class.asSubclass will get you as far as Class<T extends Enum>).
Slightly better it to switch (or keep a Map) onto available constants:
Enum<?>[] values = switch (enumNameString) {
case "SomeEnumClass" -> SomeEnumClass .values();
case "AnotherEnumClass" -> AnotherEnumClass.values();
default -> throw new Error();
};
Enum<?> en = Arrays.stream(values)
.filter(e -> e.name() == listEntry).findFirst().get();
If building a Map derived from classes, it may be easier to use EnumSet.allOf(Class<?>) than MyEnum.values(), Enum.valueOf() or Class.getEnumConstants()
You can create a Map<String, Class<?>> which contains the mapping like this:
private static final Map<String, Class<Enum<?>>> MAP;
static {
Map<String, Class<Enum<?>>> map = new HashMap<>();
map.put(SomeEnumClass.class.getSimpleName(), SomeEnumClass.class);
// your other enum classes
MAP = Collections.unmodifiableMap(map);
}
And then you can make use of Enum.valueOf(Class<Enum>, String):
Class<Enum<?>> enumClass = MAP.get(enumNameString);
if (enumClass != null) {
Collection<Enum<?>> values = new ArrayList<>;
for (String listEntry : yamlConfig.getStringList(enumNameString)) {
values.add(Enum.valueOf(enumClass, listEntry));
}
return values;
}

I want to combine the string from the scanner to list it as a class, if that makes sense [duplicate]

Example code:
int width = 5;
int area = 8;
int potato = 2;
int stackOverflow = -4;
Now, say I want to have the user input a string:
String input = new Scanner(System.in).nextLine();
Then, say the user inputs potato. How would I retrieve the variable named potato and do stuff with it? Something like this:
System.getVariable(input); //which will be 2
System.getVariable("stackOverflow"); //should be -4
I looked up some things and did not find much; I did find a reference to something called "the Reflection API," but that seems too complicated for this one simple task.
Is there a way to do this, and if so, what is it? If "Reflection" does indeed work and if it is the only way, then how would I use it to do this? The tutorial page for it has all sorts of internal stuff that I can't make any sense of.
EDIT: I need to keep the Strings in the variables for what I am doing. (I can't use a Map)
Using reflection doesn't seem like a good design for what you're doing here. It would be better to use a Map<String, Integer> for example:
static final Map<String, Integer> VALUES_BY_NAME;
static {
final Map<String, Integer> valuesByName = new HashMap<>();
valuesByName.put("width", 5);
valuesByName.put("potato", 2);
VALUES_BY_NAME = Collections.unmodifiableMap(valuesByName);
}
Or with Guava:
static final ImmutableMap<String, Integer> VALUES_BY_NAME = ImmutableMap.of(
"width", 5,
"potato", 2
);
Or with an enum:
enum NameValuePair {
WIDTH("width", 5),
POTATO("potato", 2);
private final String name;
private final int value;
private NameValuePair(final String name, final int value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
static NameValuePair getByName(final String name) {
for (final NameValuePair nvp : values()) {
if (nvp.getName().equals(name)) {
return nvp;
}
}
throw new IllegalArgumentException("Invalid name: " + name);
}
}
Variable names are only available at compiler time. Reflection only gives access to class declarations and items declared inside them, but not to local variables. I suspect that a Map of some kind will be a more appropriate solution to your real problem. Specifically, check out HashMap and TreeMap.
Instead of trying to find the value of a variable name, why don't you use a Map with a key/value pair?
Map<String, Integer> vars = new HashMap<String, Integer>();
vars.put("width",5);
vars.put("area",8);
vars.put("potato", 2);
vars.put("stackOverflow",-4);
Then you could access the inputs like so:
vars.get(input); //would be 2
vars.get("stackOverflow"); //would be -4
I have another solution without a map :
class Vars {
Integer potato, stack;
public Vars(a,b) {
potato=a;
stack=b;
}
}
Object object=(Object)new Vars(1,2);
Class<?> c = object.getClass();
Integer result=(Integer)c.getField("potato").get(object);
I have a solution for this problem that does not involve using a map. I ran into this technique because we had several variables that needed to be update based on something within the variable name itself. However, the best way to do this is by using the getters/setters rather than the variables.
After you create your class, you can access the methods by creating Method objects and invoking them individually.
public class FooClass
private String foo1;
private String foo2;
public String getFoo1();
public String getFoo2();
FooClass fooClass = new FooClass();
Method mFoo1 = fooClass.getClass().getMethod("getFoo" + increment + "()");
mFoo1 .invoke(fooClass);
However, this would not be limited to only incremental numbers, as long as you can get the string to match the method exactly.
String value = "Potato";
Method mPotato = myClass.getClass().getMethod("get" + value+ "()");
mPotato.invoke(myClass);
Very redundant, but you can keep your variable names when using a map:
int width = 5;
int area = 8;
int potato = 2;
int stackOverflow = -4;
Map<String, Integer> map = new HashMap<>();
map.put("width", width);
map.put("area", area);
map.put("potato", potato);
map.put("stackOverflow", stackOverflow);
But a statement like this:
width = 42;
would not change the value in the Map:
String input = "width";
map.get(input); // <-- still returns 5.
Only a new call of put fixes that:
width = 42;
map.put("width", width);
// or
map.put("width", 42);

How do I store Enum types into a map?

I am trying to store and retrieve an enum class so I can later find the correct type (based on configuration) and call ValueOf on it to parse a string. How do I put/get and then call the ValueOf?
In pseudo code it would look something like this:
enum MyType { VAL1, VAL2 };
enum MyColors { BLUE, RED };
Map<String, Class> map = Maps.newHashMap();
map.put("key1", MyType.class); //this is the MyType enum above
map.put("colors", MyColors.class); //this is the MyColors enum above
...
String inputType = "colors";
String inputValue = "BLUE";
Class c = map.get(inputType);
assertEquals(MyColors.BLUE, c.ValueOf(inputValue)); //here I want MyColors.ValueOf() to get called
Class c2 = map.get("key1");
assertEquals(MyType.VAL1, c.ValueOf("VAL1")); //here I want MyType.ValueOf() to get called
How can I do this?
To give some background on why I am doing this - I have multiple such enum types and I get an input which tells me what kind of enum it is (in text) and one of the values from the enum, so I want to look up the enum class from the map and then call the static ValueOf on it which will parse correctly.
Note - I do not want to store MyType objects in the map, I want to store class references
Thanks!
You can use java.lang.Enum.valueOf(Class enumType, String name)
enum MyType { VAL1, VAL2 };
Map enumMap = Maps.newHashMap();
map.put("key1", MyType.class);
...
Class c = map.get("key1");
assertEquals(MyType.VAL1, Enum.valueOf(c, "VAL1"));
Instead of using Class as the second type, use your enum's type.
enum MyType { VAL1, VAL2 };
public static void main (String args[]){
Map<String,MyType> map = new HashMap<String,MyType>();
}
public enum MyType {
VAL1("value1"),
VAL2("value2");
private valueString;
private enum MyType(String val) {
valueString = val;
}
#Override
public String toString() {
return valueString;
}
}
It looks like you want to use the String as a key so define your MyType enum and then to set and get values:
Map<String, MyType> enumMap = new HashMap<String, MyType>();
enumMap.put("key", MyType.VAL1);
...
MyType keyVal = enumMap.get("key1");
String settingValue = keyVal.toString(); // will be "value1"

Categories

Resources