Гетерогенный выбор типов в HashMaps
Возможно ли иметь HashMap, который принимает унифицированный ключ (скажем, типа 'String'), но имеет значение, которое меняется. Для примера рассмотрим HashMap m. Я хочу следующую функциональность:
m.get("UID1"); // This fetches a String value
m.get("UID2"); // This fetches another type like a class instance
Затем, в зависимости от типа извлеченного значения, может быть выполнена дальнейшая обработка.
3 ответа
Вы могли бы сделать что-то вроде этого:
HashMap<String, Object> m = new HashMap<>();
//Retreive a string:
String myString = (String) m.get("UID1");
//Retreive a variable of any type
MyType variable = (MyType) m.get("UID2");
Или, если возможно, вы можете установить тип вашего hashmap на нужный вам тип, и если вы хотите String, вы можете сделать toString().
Например:
HashMap<String, MyObject> m = new HashMap<>();
//Retreive the string representation of your object:
String myString = m.get("UID1").toString();
//Retreive the object:
MyObject object = m.get("UID2");
Если вы хотите использовать Strings в качестве ключей, вы потеряете безопасность типов, и нет никакого способа обойти это. Вы просто будете отказываться от всей информации, которую имеет компилятор, и вы будете вынуждены предоставить ее самостоятельно.
Вместо этого вы можете использовать некоторый класс-оболочку для String, который также содержит информацию о типе. Таким образом, вы можете получить разумное количество типов безопасности.
Вот пример для класса Foo, который упаковывает HashMap и предоставляет методы safe put и get типа:
public class Foo {
static class TypedKey<T> {
final Class<T> clazz;
final String id;
public TypedKey(Class<T> clazz, String id) {
this.clazz = clazz;
this.id = id;
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof TypedKey)) {
return false;
}
return id.equals(((TypedKey<?>) obj).id);
}
@Override
public String toString() {
return id;
}
}
static final TypedKey<String> STRING_KEY = new TypedKey<>(String.class, "String");
static final TypedKey<Integer> INTEGER_KEY = new TypedKey<>(Integer.class, "Integer");
public static void main(String[] args) {
Foo foo = new Foo();
foo.put(STRING_KEY, "bar");
String bar = foo.get(STRING_KEY);
foo.put(INTEGER_KEY, 42);
int baz = foo.get(INTEGER_KEY);
foo.put(STRING_KEY, 42);
}
final Map<TypedKey<?>, Object> map = new HashMap<>();
public <T> void put(TypedKey<T> key, T value) {
map.put(key, value);
}
@SuppressWarnings("unchecked")
public <T> T get(TypedKey<T> key) {
return (T) map.get(key);
}
}
Подумайте об использовании классов в качестве ключей, а не укусов. Это даст вам возможность сделать это безопасным для типов способом, который описал Джошуа Блох как "типичный безопасный гетерогенный контейнер":
public static class Container {
private Map<Class<?>, Object> store = new HashMap<>();
public <T> void put(Class<T> type, T value) {
store.put(Objects.requireNonNull(type), value);
}
public <T> T get(Class<T> type) {
return type.cast(store.get(type));
}
}
Вы можете поместить значение и получить его безопасным способом, например так:
Container container = new Container();
container.put(String.class, "test");
container.put(Integer.class, Integer.valueOf(5));
String s = container.get(String.class);
Integer i = container.get(Integer.class);
Но это будет работать, только если у вас есть только одно значение для каждого типа.