具有受键类型参数限制的值的 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
Java map with values limited by key's type parameter
提问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); } }
回答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 instanceof
Class.
不,你不能直接这样做。您需要编写一个包装类Map<Class, Object>
来强制对象将是instanceof
类。
回答by Yuval Adam
T
as 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 Map
with 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 T
is never used in this class! 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 类。请注意,此类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 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.
可以创建一个类来存储类型安全键到值的映射,并在必要时进行转换。cast inget
方法是安全的,因为在 using 之后new Key<CharSequence>()
,不可能安全地将其转换为Key<String>
or 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. Alternatively, it's possible to override equals
to 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> {
}
}