I have the following structure with a parent class, several subclasses, and each of the subclasses has its own Enum with various keys. The parent class is required to have a map with keys characterized by the Enum, but the Enum type is abstract - it's determined by which subclass is being instantiated.
I would like to use generic types to require that the Enum type is from that specific class - so you can only add APPLE to Fruits and CELERY to Veggies and never vice versa. My application also requires any Food object to be able to query its possible types (like to say, print out all of the types that are possible, not just the types we have in the Map).
How can I use generic types to make that all work? I am getting the compiler error on the line with the EnumSet declaration Uncompilable source code - type argument E is not within bounds of type-variable E
.
import java.util.*;
public class sandbox {
private static class Food<E extends Enum> {
public Map<E, Integer> qty = new HashMap<>();
public EnumSet<E> types = EnumSet.allOf(E);
}
private static class Fruit extends Food<Fruit.Type> {
public static enum Type {
APPLE,
ORANGE
}
}
private static class Veggie extends Food<Veggie.Type> {
public static enum Type {
CUCUMBER,
CELERY
}
}
public static void main(String[] args) throws Exception {
Fruit mondays = new Fruit();
mondays.qty.put(Fruit.Type.APPLE, 1);
mondays.qty.put(Fruit.Type.ORANGE, 3);
Veggie tuesdays = new Veggie();
tuesdays.qty.put(Veggie.Type.CELERY, 10);
mondays.qty.forEach((f, qty) -> System.out.println("Buying " f " in qty " qty));
tuesdays.qty.forEach((f, qty) -> System.out.println("Buying " f " in qty " qty));
mondays.types.forEach(System.out::print);
}
}
CodePudding user response:
I would take advantage of the fact that enums can implement interfaces, using an interface as the type constraint of Food
instead of Enum<>
. Then make Food
abstract so that each specific food subtype can declare its enum type.
private static interface FoodType {}
private static abstract class Food<T extends FoodType> {
public Map<T, Integer> qty = new HashMap<>();
protected abstract Set<T> getTypes();
}
private static class Fruit extends Food<Fruit.Type> {
public static enum Type implements FoodType {
APPLE,
ORANGE
}
@Override
protected Set<Type> getTypes() {
return EnumSet.allOf(Type.class);
}
}
private static class Veggie extends Food<Veggie.Type> {
public static enum Type implements FoodType {
CUCUMBER,
CELERY
}
@Override
protected Set<Type> getTypes() {
return EnumSet.allOf(Type.class);
}
}
If you want be slightly more DRY and avoid implementing getTypes()
in every concrete Food
subclass, you can pass in the array of enum values in a protected constructor. Like this:
private static interface FoodType { }
private static abstract class Food<T extends FoodType> {
public Map<T, Integer> qty;
public Set<T> types;
protected Food(T[] types) {
this.types = Set.of(types);
this.qty = new HashMap<>();
}
}
private static class Fruit extends Food<Fruit.Type> {
protected Fruit() {
super(Type.values());
}
public static enum Type implements FoodType {
APPLE,
ORANGE;
}
}
private static class Veggie extends Food<Veggie.Type> {
protected Veggie() {
super(Type.values());
}
public static enum Type implements FoodType {
CUCUMBER,
CELERY
}
}