Карта Java со значениями, ограниченными параметром типа ключа
Есть ли способ в Java иметь карту, где параметр типа значения привязан к параметру типа ключа? Я хочу написать что-то вроде следующего:
public class Foo {
// This declaration won't compile - what should it be?
private static Map<Class<T>, T> defaultValues;
// These two methods are just fine
public static <T> void setDefaultValue(Class<T> clazz, T value) {
defaultValues.put(clazz, value);
}
public static <T> T getDefaultValue(Class<T> clazz) {
return defaultValues.get(clazz);
}
}
То есть я могу сохранить любое значение по умолчанию для объекта Class, при условии, что тип значения совпадает с типом объекта Class. Я не понимаю, почему это не должно быть разрешено, так как я могу при настройке / получении значений убедиться, что типы правильные.
РЕДАКТИРОВАТЬ: Спасибо Cletus за его ответ. На самом деле мне не нужны параметры типа на самой карте, так как я могу обеспечить согласованность в методах, которые получают / устанавливают значения, даже если это подразумевает использование немного уродливых приведений.
5 ответов
Вы не пытаетесь реализовать типичный гетерогенный контейнерный шаблон Джошуа Блоха? В принципе:
public class Favorites { private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>(); public <T> void setFavorite(Class<T> klass, T thing) { favorites.put(klass, thing); } public <T> T getFavorite(Class<T> klass) { return klass.cast(favorites.get(klass)); } public static void main(String[] args) { Favorites f = new Favorites(); f.setFavorite(String.class, "Java"); f.setFavorite(Integer.class, 0xcafebabe); String s = f.getFavorite(String.class); int i = f.getFavorite(Integer.class); } }
Вопрос и ответы заставили меня придумать такое решение: карта типов объектов. Вот код Прецедент:
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
public class TypedMapTest {
private final static TypedMapKey<String> KEY1 = new TypedMapKey<String>( "key1" );
private final static TypedMapKey<List<String>> KEY2 = new TypedMapKey<List<String>>( "key2" );
@Test
public void testGet() throws Exception {
TypedMap map = new TypedMap();
map.set( KEY1, null );
assertNull( map.get( KEY1 ) );
String expected = "Hallo";
map.set( KEY1, expected );
String value = map.get( KEY1 );
assertEquals( expected, value );
map.set( KEY2, null );
assertNull( map.get( KEY2 ) );
List<String> list = new ArrayList<String> ();
map.set( KEY2, list );
List<String> valueList = map.get( KEY2 );
assertEquals( list, valueList );
}
}
Ключевой класс:
public class TypedMapKey<T> {
private String key;
public TypedMapKey( String key ) {
this.key = key;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ( ( key == null ) ? 0 : key.hashCode() );
return result;
}
@Override
public boolean equals( Object obj ) {
if( this == obj ) {
return true;
}
if( obj == null ) {
return false;
}
if( getClass() != obj.getClass() ) {
return false;
}
TypedMapKey<?> other = (TypedMapKey<?>) obj;
if( key == null ) {
if( other.key != null ) {
return false;
}
} else if( !key.equals( other.key ) ) {
return false;
}
return true;
}
@Override
public String toString() {
return key;
}
}
TypedMap.java:
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class TypedMap implements Map<Object, Object> {
private Map<Object, Object> delegate;
public TypedMap( Map<Object, Object> delegate ) {
this.delegate = delegate;
}
public TypedMap() {
this.delegate = new HashMap<Object, Object>();
}
@SuppressWarnings( "unchecked" )
public <T> T get( TypedMapKey<T> key ) {
return (T) delegate.get( key );
}
@SuppressWarnings( "unchecked" )
public <T> T remove( TypedMapKey<T> key ) {
return (T) delegate.remove( key );
}
public <T> void set( TypedMapKey<T> key, T value ) {
delegate.put( key, value );
}
// --- Only calls to delegates below
public void clear() {
delegate.clear();
}
public boolean containsKey( Object key ) {
return delegate.containsKey( key );
}
public boolean containsValue( Object value ) {
return delegate.containsValue( value );
}
public Set<java.util.Map.Entry<Object, Object>> entrySet() {
return delegate.entrySet();
}
public boolean equals( Object o ) {
return delegate.equals( o );
}
public Object get( Object key ) {
return delegate.get( key );
}
public int hashCode() {
return delegate.hashCode();
}
public boolean isEmpty() {
return delegate.isEmpty();
}
public Set<Object> keySet() {
return delegate.keySet();
}
public Object put( Object key, Object value ) {
return delegate.put( key, value );
}
public void putAll( Map<? extends Object, ? extends Object> m ) {
delegate.putAll( m );
}
public Object remove( Object key ) {
return delegate.remove( key );
}
public int size() {
return delegate.size();
}
public Collection<Object> values() {
return delegate.values();
}
}
Нет, ты не можешь сделать это напрямую. Вам нужно написать класс-оболочку вокруг Map<Class, Object>
чтобы обеспечить выполнение этого объекта будет instanceof
Учебный класс.
Можно создать класс, в котором хранится карта типа "безопасный ключ" со значением, и при необходимости он приводится. Актерский состав в get
метод безопасен, как после использования new Key<CharSequence>()
, невозможно безопасно привести его к Key<String>
или же Key<Object>
Таким образом, система типов обеспечивает правильное использование класса.
Key
класс должен быть окончательным, иначе пользователь может переопределить equals
и вызвать небезопасность типа, если два элемента с разными типами должны быть равны. В качестве альтернативы, можно переопределить equals
чтобы быть окончательным, если вы хотите использовать наследование, несмотря на проблемы с ним.
public final class TypeMap {
private final Map<Key<?>, Object> m = new HashMap<>();
public <T> T get(Key<? extends T> key) {
// Safe, as it's not possible to safely change the Key generic type,
// hash map cannot be accessed by an user, and this class being final
// to prevent serialization attacks.
@SuppressWarnings("unchecked")
T value = (T) m.get(key);
return value;
}
public <T> void put(Key<? super T> key, T value) {
m.put(key, value);
}
public static final class Key<T> {
}
}
Вы можете использовать ниже 2 класса, класс карты: GenericMap, класс карты-ключа: GenericKey
Например:
// Create a key includine Type definition
public static final GenericKey<HttpServletRequest> REQUEST = new GenericKey<>(HttpServletRequest.class, "HttpRequestKey");
public void example(HttpServletRequest requestToSave)
{
GenericMap map = new GenericMap();
// Saving value
map.put(REQUEST, requestToSave);
// Getting value
HttpServletRequest request = map.get(REQUEST);
}
Преимущества
- Он заставляет пользователя вводить и получать правильные типы из-за ошибки компиляции
- Он делает оболочку для вас внутри
- Generic Key помогает избежать записи типа класса каждый раз, когда вы вызываете put(..) или get
- Никаких опечаток, например, если ключ типа String
GenericMap
public class GenericMap
{
private Map<String, Object> storageMap;
protected GenericMap()
{
storageMap = new HashMap<String, Object>();
}
public <T> T get(GenericKey<T> key)
{
Object value = storageMap.get(key.getKey());
if (value == null)
{
return null;
}
return key.getClassType().cast(value);
}
/**
* @param key GenericKey object with generic type - T (it can be any type)
* @param object value to put in the map, the type of 'object' mast be - T
*/
public <T> void put(GenericKey<T> key, T object)
{
T castedObject = key.getClassType().cast(object);
storageMap.put(key.getKey(), castedObject);
}
@Override
public String toString()
{
return storageMap.toString();
}
}
GenericKey
public class GenericKey<T>
{
private Class<T> classType;
private String key;
@SuppressWarnings("unused")
private GenericKey()
{
}
public GenericKey(Class<T> iClassType, String iKey)
{
this.classType = iClassType;
this.key = iKey;
}
public Class<T> getClassType()
{
return classType;
}
public String getKey()
{
return key;
}
@Override
public String toString()
{
return "[classType=" + classType + ", key=" + key + "]";
}
}
T
как тип должен быть определен в общем случае в экземпляре класса. Следующий пример работает:
public class Test<T> {
private Map<Class<T>, T> defaultValues;
public void setDefaultValue(Class<T> clazz, T value) {
defaultValues.put(clazz, value);
}
public T getDefaultValue(Class<T> clazz) {
return defaultValues.get(clazz);
}
}
Кроме того, вы можете использовать ответ Пола Томблина и обернуть Map
с вашим собственным объектом, который будет применять этот тип обобщений.