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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/416540/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 14:23:25  来源:igfitidea点击:

Java map with values limited by key's type parameter

javagenerics

提问by Ashley Mercer

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? What I want to write is something like the following:

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

That is, I can store any default value against a Class object, provided the value's type matches that of the Class object. I don't see why this shouldn't be allowed since I can ensure when setting/getting values that the types are correct.

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

EDIT: Thanks to cletus for his answer. 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.

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

采纳答案by cletus

You're not trying to implement Joshua Bloch's typesafe hetereogeneous container pattern are you? Basically:

您不是在尝试实现 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);
  }
}
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 版)本演示文稿

回答by Paul Tomblin

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 instanceofClass.

不,你不能直接这样做。您需要编写一个包装类Map<Class, Object>来强制对象将是instanceof类。

回答by Yuval Adam

Tas a type must be defined generically in the class instance. The following example works:

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

}

Alternatively, you can use Paul Tomblin's answer, and wrap the Mapwith your own object which will enforce this type of generics.

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

回答by Aaron Digulla

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. Note that the type Tis never used in this class! It's purely for the purpose of type casting when reading the value out of the map. The field keyonly gives the key a name.

这是 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:

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

}

回答by Konrad Borowski

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 getmethod 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.

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

The Keyclass needs to be final, as otherwise an user could override equalsand cause type-unsafety if two elements with different types were to be equal. Alternatively, it's possible to override equalsto be final if you want to use inheritance despite the issues with it.

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