簡體   English   中英

具有受鍵類型參數限制的值的 Java 映射

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

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 的回答。 我實際上並不需要地圖本身的類型參數,因為我可以確保獲取/設置值的方法的一致性,即使這意味着使用一些稍微難看的強制轉換。

您不是在嘗試實現 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(第 2 版)本演示文稿

問題和答案讓我想出了這個解決方案:類型安全對象映射 這是代碼。 測試用例:

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

}

不,你不能直接這樣做。 您需要圍繞Map<Class, Object>編寫一個包裝類Map<Class, Object>以強制該 Object 將是instanceof Class。

可以創建一個類來存儲類型安全鍵到值的映射,並在必要時進行轉換。 get方法中的強制轉換是安全的,因為在使用new Key<CharSequence>() ,不可能安全地將其強制轉換為Key<String>Key<Object> ,因此類型系統強制正確使用類。

Key類需要是 final 的,否則如果兩個不同類型的元素equals ,用戶可能會覆蓋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> {
    }
}

您可以使用以下 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 + "]";
    }
}

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 ,這將強制執行這種類型的泛型。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM