【问题标题】:Java map with values limited by key's type parameter值受键类型参数限制的 Java 映射
【发布时间】:2010-09-29 19:32:42
【问题描述】:

在 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 的回答。我实际上并不需要地图本身的类型参数,因为我可以确保获取/设置值的方法的一致性,即使这意味着使用一些稍微难看的强制转换。

【问题讨论】:

    标签: java generics


    【解决方案1】:

    您不是在尝试实现 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

    【讨论】:

    • 如果值本身是通用的怎么办?例如,您需要存储PrettyPrinter&lt;T&gt;,而不是存储Strings 和ints,其中T 是用作映射中键的类型标记?
    • @Lucas Guava 为此提供了TypeToInstanceMap;以及复制 Bloch 的 Favorites API 的 ClassToInstanceMap
    【解决方案2】:

    问题和答案让我想出了这个解决方案: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 );
        }
    }
    

    这是 Key 类。注意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;
        }
    }
    

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

    【讨论】:

      【解决方案3】:

      不,你不能直接这样做。您需要围绕 Map&lt;Class, Object&gt; 编写一个包装类,以强制 Object 为 instanceof 类。

      【讨论】:

        【解决方案4】:

        可以创建一个类,该类将安全键类型的映射存储到一个值,并在必要时进行强制转换。 get 方法中的强制转换是安全的,因为使用 new Key&lt;CharSequence&gt;() 后,无法安全地将其强制转换为 Key&lt;String&gt;Key&lt;Object&gt;,因此类型系统强制正确使用类。

        Key 类必须是最终的,否则如果两个不同类型的元素相等,用户可能会覆盖 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> {
            }
        }
        

        【讨论】:

          【解决方案5】:

          您可以使用以下 2 个类,Map 类:GenericMap,Map-Key 类: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时写入类类型>
          • 没有拼写错误,例如键是“字符串”类型

          通用地图

          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 + "]";
              }
          }
          

          【讨论】:

            【解决方案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);
                }
            
            }
            

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

            【讨论】:

            • 发帖者希望有一个从任意类到这些类的默认值的映射。这允许仅来自一个类的映射。
            猜你喜欢
            • 2017-08-08
            • 2013-08-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-11-28
            • 1970-01-01
            • 1970-01-01
            • 2020-05-31
            相关资源
            最近更新 更多