有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

值受键的类型参数限制的泛型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);
    }
}

也就是说,只要值的类型与类对象的类型匹配,我就可以针对类对象存储任何默认值。我不明白为什么不允许这样做,因为我可以确保在设置/获取值时类型是正确的

编辑:谢谢克莱特斯的回答。我实际上不需要映射本身的类型参数,因为我可以确保获取/设置值的方法的一致性,即使这意味着使用一些稍微难看的强制转换


共 (6) 个答案

  1. # 1 楼答案

    您可以使用以下两个类,映射类: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);
    }
    

    优势

    • 它通过编译错误强制用户输入和获取正确的类型
    • 它在里面为你做外壳
    • 泛型键有助于避免每次调用put(..)时编写类类型或获取
    • 没有打字错误,比如键是“字符串”类型

    通用地图

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

    通用密钥

    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 + "]";
        }
    }
    
  2. # 2 楼答案

    不,你不能直接做。您需要围绕Map<Class, Object>编写一个包装器类,以强制该对象为instanceof

  3. # 3 楼答案

    可以创建一个类来存储类型为safe key的映射到值,并在必要时强制转换。强制转换get方法是安全的,因为在使用new Key<CharSequence>()之后,不可能将其安全地强制转换为Key<String>Key<Object>,因此类型系统强制正确使用类

    Key类必须是final,否则,如果两个不同类型的元素相等,用户可能会重写equals并导致类型不安全。或者,如果您想使用继承,尽管它存在问题,也可以将equals重写为final

    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> {
        }
    }
    
  4. # 4 楼答案

    你不是在尝试实现Joshua Bloch的类型安全异构容器模式吧?基本上:

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

    来自Effective Java (2nd edition)this presentation

  5. # 5 楼答案

    这个问题和答案让我想到了这个解决方案:Type-safe object map。这是代码。测试用例:

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

    这是重点课程。注意,类型T从未在这个类中使用过!这纯粹是为了在从映射中读取值时进行类型转换。字段key只给出键的名称

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

    类型映射。爪哇:

    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();
        }
    
    }
    
  6. # 6 楼答案

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

    或者,您可以使用paultomblin的答案,并用您自己的对象包装Map,这将强制执行这种类型的泛型