简体   繁体   English

具有受键类型参数限制的值的 Java 映射

[英]Java map with values limited by key's type parameter

Is there a way in Java to have a map where the type parameter of a value is tied to the type parameter of a key? Java 中是否有一种方法可以在映射中将值的类型参数绑定到键的类型参数? What I want to write is something like the following:我想写的内容如下:

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

That is, I can store any default value against a Class object, provided the value's type matches that of the Class object.也就是说,我可以针对 Class 对象存储任何默认值,前提是该值的类型与 Class 对象的类型相匹配。 I don't see why this shouldn't be allowed since I can ensure when setting/getting values that the types are correct.我不明白为什么不允许这样做,因为我可以确保在设置/获取值时类型是正确的。

EDIT: Thanks to cletus for his answer.编辑:感谢 cletus 的回答。 I don't actually need the type parameters on the map itself since I can ensure consistency in the methods which get/set values, even if it means using some slightly ugly casts.我实际上并不需要地图本身的类型参数,因为我可以确保获取/设置值的方法的一致性,即使这意味着使用一些稍微难看的强制转换。

You're not trying to implement Joshua Bloch's typesafe hetereogeneous container pattern are you?您不是在尝试实现 Joshua Bloch 的类型安全异构容器模式,是吗? Basically:基本上:

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

From Effective Java (2nd edition) and this presentation .来自Effective Java(第 2 版)本演示文稿

The question and the answers made me come up with this solution: Type-safe object map .问题和答案让我想出了这个解决方案:类型安全对象映射 Here is the code.这是代码。 Test case:测试用例:

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

This is the Key class.这是 Key 类。 Note that the type T is never used in this class!请注意,此类中从未使用过类型T It's purely for the purpose of type casting when reading the value out of the map.这纯粹是为了在从地图中读取值时进行类型转换。 The field key only gives the key a name.字段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: 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();
    }

}

No, you can't do it directly.不,你不能直接这样做。 You'll need to write a wrapper class around Map<Class, Object> to enforce that Object will be instanceof Class.您需要围绕Map<Class, Object>编写一个包装类Map<Class, Object>以强制该 Object 将是instanceof Class。

It's possible to create a class which stores a map of type safe key to a value, and cast when necessary.可以创建一个类来存储类型安全键到值的映射,并在必要时进行转换。 The cast in get method is safe, as after using new Key<CharSequence>() , it's not possible to safely cast it to Key<String> or Key<Object> , so the type system enforces the correct usage of a class. get方法中的强制转换是安全的,因为在使用new Key<CharSequence>() ,不可能安全地将其强制转换为Key<String>Key<Object> ,因此类型系统强制正确使用类。

The Key class needs to be final, as otherwise an user could override equals and cause type-unsafety if two elements with different types were to be equal. Key类需要是 final 的,否则如果两个不同类型的元素equals ,用户可能会覆盖equals并导致类型不安全。 Alternatively, it's possible to override equals to be final if you want to use inheritance despite the issues with it.或者,如果您想使用继承尽管存在问题,则可以覆盖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> {
    }
}

You can use below 2 classes, Map class: GenericMap , Map-Key class: GenericKey您可以使用以下 2 个类,Map 类: GenericMap ,Map-Key 类: GenericKey

For example:例如:

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

Advantages好处

  • It forces the user to put and get correct types by compilation error它强制用户通过编译错误放置和获取正确的类型
  • It's doing casing for you inside它在里面为你做套管
  • Generic Key helps to avoid write the class type each time you calling put(..) or get Generic Key 有助于避免每次调用put(..)get时编写类类型
  • No typo mistakes, like if key is 'String' type没有拼写错误,比如如果键是“字符串”类型

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 as a type must be defined generically in the class instance. T作为类型必须在类实例中进行泛型定义。 The following example works:以下示例有效:

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

}

Alternatively, you can use Paul Tomblin's answer, and wrap the Map with your own object which will enforce this type of generics.或者,您可以使用 Paul Tomblin 的答案,并用您自己的对象包装Map ,这将强制执行这种类型的泛型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM