2009-01-06 5 views
28

Gibt es in Java eine Map, in der der Typparameter eines Werts an den Typparameter eines Schlüssels gebunden ist? Was ich will, schreiben, so etwas wie die folgende ist:Java-Map mit Werten, die durch den Schlüsseltyp-Parameter begrenzt sind

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

Das heißt, ich kann speichern alle Standardwert mit einem Class-Objekt, sofern Art der Wert der dem des Klassenobjekt. Ich sehe nicht, warum dies nicht erlaubt sein sollte, da ich sicherstellen kann, wenn Werte eingestellt/erhalten werden, dass die Typen korrekt sind.

EDIT: Danke an Cletus für seine Antwort. Ich brauche eigentlich nicht die Typparameter auf der Karte selbst, da ich Konsistenz in den Methoden sicherstellen kann, die Werte erhalten/setzen, auch wenn es bedeutet, einige leicht hässliche Umwandlungen zu verwenden.

Antwort

47

Sie versuchen nicht, Joshua Blochs typensicheres heterogene Containermuster zu implementieren, oder? Grundsätzlich gilt:

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

Von Effective Java (2nd edition) und this presentation.

+27

Was ist, wenn der Wert selbst generisch ist? Zum Beispiel, anstatt 'String's und' int's zu speichern, müssen Sie 'PrettyPrinter ' speichern, wobei 'T' das Typ-Token ist, das als Schlüssel in der Karte verwendet wird? – Lucas

+3

@Lucas Guava stellt zu diesem Zweck ['TypeToInstanceMap'] bereit (https://google.github.io/guava/releases/snapshot/api/docs/com/google/common/reflect/TypeToInstanceMap.html); zusammen mit ["ClassToInstanceMap"] (https://github.com/google/guava/wiki/NewCollectionTypesExplained#classtoinstancemap), das Blochs 'Favorites'-API repliziert. – dimo414

2

Nein, Sie können es nicht direkt tun. Sie müssen eine Wrapperklasse um Map<Class, Object> schreiben, um zu erzwingen, dass das Objekt instanceof Klasse ist.

-2

T als ein Typ muss generisch in der Klasseninstanz definiert werden. Das folgende Beispiel funktioniert:

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

} 

Alternativ können Sie Paul Tomblin Antwort verwenden, und wickeln Sie die Map mit Ihrem eigenen Objekt, das diese Art von Generika erzwingen wird.

+6

Das Plakat, das eine Zuordnung von beliebigen Klassen haben will Werte dieser Klassen auf Standard. Dies ermöglicht eine Zuordnung von nur einer Klasse. – Avi

2

Die Frage und die Antworten brachten mich zu dieser Lösung: Type-safe object map. Hier ist der Code. Testfall:

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-Klasse:

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

} 
Verwandte Themen