2009-01-06 10 views
28

¿Existe alguna manera en Java de tener un mapa donde el parámetro de tipo de un valor esté vinculado al parámetro de tipo de una clave? Lo que yo quiero escribir es algo como lo siguiente:Mapa de Java con valores limitados por el parámetro de tipo de clave

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

Es decir, puedo almacenar cualquier valor predeterminado contra un objeto de clase, siempre el tipo del valor coincide con el de la clase de objeto. No veo por qué no debería permitirse esto, ya que puedo garantizar al establecer/obtener valores que los tipos son correctos.

EDITAR: Gracias a cletus por su respuesta. En realidad, no necesito los parámetros de tipo en el mapa en sí, ya que puedo garantizar la coherencia en los métodos que obtienen/establecen valores, incluso si eso significa usar moldes ligeramente feos.

Respuesta

47

No está tratando de implementar el patrón de contenedor heterogéneo seguro de Joshua Bloch, ¿verdad? Básicamente:

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

De Effective Java (2nd edition) y this presentation.

+27

¿Qué sucede si el valor en sí es genérico? Por ejemplo, en lugar de almacenar 'String's y' int's, debe almacenar 'PrettyPrinter ' donde 'T' es el token de tipo utilizado como clave en el mapa. – Lucas

+3

@Lucas Guava proporciona ['TypeToInstanceMap'] (https://google.github.io/guava/releases/snapshot/api/docs/com/google/common/reflect/TypeToInstanceMap.html) para este propósito; junto con ['ClassToInstanceMap'] (https://github.com/google/guava/wiki/NewCollectionTypesExplained#classtoinstancemap) que replica la API' Favoritos' de Bloch. – dimo414

2

No, no puede hacerlo directamente. Tendrá que escribir una clase contenedora alrededor del Map<Class, Object> para exigir que el Objeto sea instanceof Clase.

-2

T como un tipo debe definirse genéricamente en la instancia de la clase. El siguiente ejemplo funciona:

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

} 

Como alternativa, puede utilizar la respuesta de Pablo Tomblin, y envolver el Map con su propio objeto que va a hacer cumplir este tipo de medicamentos genéricos.

+6

El cartel quiere tener un mapeo de clases arbitrarias a los valores predeterminados de esas clases. Esto permite un mapeo de solo una clase. – Avi

2

La pregunta y las respuestas me hicieron pensar en esta solución: Type-safe object map. Aquí está el código. caso de prueba:

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

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

} 
Cuestiones relacionadas