5

I want to define map in Java, which keys are enums, and types of value depend of key. For example, suppose that we have following enum type:

enum KeyType {
        HEIGHT(Integer.class),
        NAME(String.class),
        WEIGHT(Double.class)
       // constructor and getter for Class field

}

and some map:

Map< KeyType, Object > map = new EnumMap<>(KeyType.class);

Is there any simple and safe way to write generic method:

public < T > T get(KeyType key) {
//...
}

that would get value from that map and cast it to corresponding with type class?

Marcin Nowak
  • 77
  • 2
  • 6
  • 1
    Sorry, but exactly this method is offered by [EnumMap](http://docs.oracle.com/javase/8/docs/api/java/util/EnumMap.html#get-java.lang.Object-), do I misunderstand? – xerx593 Apr 20 '15 at 17:16
  • 1
    I believe that no since on compile time it cannot be known which type would be returned--you are not declaring it, but pass as an argument. – Alex Salauyou Apr 20 '15 at 17:17
  • 1
    When using this `get` method, based on what information do you think the compiler can infer the correct type of `T`? Hint: There is no such information. Conclusion, You can get back an object of the correct type, but the method cannot be generic. And that automatically leads to @xerx593's comment: Use `Map.get(...)` instead. – Seelenvirtuose Apr 20 '15 at 17:18
  • 1
    You would need a parameter that uses `T`. But as enums do not allow type parameters, you would have to use a class or something. – Bubletan Apr 20 '15 at 17:21

3 Answers3

2

UPDATE!!!: With this in mind:

enum KeyType {

    //your enums ...
    private final Class val;

    //constructor ...

    //and generic(!) access to the class field:
    <T> Class<T> val() {
        return val;
    }
}

...this is possible:

public <T> T get(KeyType key) {
    return (T) key.val().cast(map.get(key));
}
xerx593
  • 12,237
  • 5
  • 33
  • 64
  • 1
    See also: https://stackoverflow.com/a/6795110/1599699 https://stackoverflow.com/a/507658/1599699 – Andrew Sep 19 '18 at 14:58
0

Your map definition would need to be

Map< KeyType, ?> map = new EnumMap<>(KeyType.class);

If you specify Object as a generic type, only actual instances of Object are allowed, not sub-types.

I don't believe there's any straight forward, generic way (no pun intended) to do what you want. You would need to create some mapping function that translates the object to the correct type based on the enum.

MadConan
  • 3,749
  • 1
  • 16
  • 27
0

You can't do it with enums. But you could write a "fake" enum (the way Java code did it before Java 1.5, with private constructors and public static instances), and attach a generic type to each constant:

import java.io.Serializable;
import java.util.Map;

public final class KeyType<T>
implements Serializable {
    private static final long serialVersionUID = 1;

    public static final KeyType<Integer> HEIGHT =
        new KeyType<>("HEIGHT", Integer.class);

    public static final KeyType<String> NAME =
        new KeyType<>("NAME", String.class);

    public static final KeyType<Double> WEIGHT =
        new KeyType<>("WEIGHT", Double.class);

    private static final KeyType<?>[] allValues = {
        HEIGHT, NAME, WEIGHT
    };

    /** @serial */
    private final String name;

    /** @serial */
    private final Class<T> type;

    private KeyType(String name,
                    Class<T> type) {
        this.name = name;
        this.type = type;
    }

    public String name() {
        return name;
    }

    public Class<T> getType() {
        return type;
    }

    @Override
    public String toString() {
        return name();
    }

    public static KeyType<?>[] values() {
        return allValues.clone();
    }

    public static KeyType<?> valueOf(String name) {
        for (KeyType<?> value : allValues) {
            if (value.name.equals(name)) {
                return value;
            }
        }
        throw new IllegalArgumentException("No such value: \"" + name + "\"");
    }

    @Override
    public boolean equals(Object obj) {
        return (obj instanceof KeyType &&
            this.name.equals(((KeyType<?>) obj).name));
    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }

    public T getValue(Map<KeyType<?>, ?> map) {
        return type.cast(map.get(this));
    }
}
VGR
  • 40,506
  • 4
  • 48
  • 63