Карта Java со значениями, ограниченными параметром типа ключа - PullRequest
35 голосов
/ 06 января 2009

Есть ли способ в 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 ]

53 голосов
/ 06 января 2009

Ты не пытаешься реализовать типичный гетерогенный контейнерный шаблон Джошуа Блоха? В основном:

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);
  }
}

Из Эффективная Java (2-е издание) и эта презентация .

3 голосов
/ 28 мая 2010

Вопрос и ответы заставили меня прийти к такому решению: Типобезопасная карта объекта . Вот код Тестовый кейс:

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();
    }

}
2 голосов
/ 06 января 2009

Нет, вы не можете сделать это напрямую. Вам нужно написать класс-оболочку около Map<Class, Object>, чтобы обеспечить, что Object будет instanceof Class.

0 голосов
/ 09 ноября 2018

Можно создать класс, в котором будет храниться карта с ключом безопасного типа для значения, и при необходимости он будет приведен. Метод приведения в 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> {
    }
}
0 голосов
/ 06 января 2009

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 своим собственным объектом, который будет применять этот тип обобщений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...