2012-05-21 32 views
7

Espero que alguien me puede ayudar a salir de esta confusión.Java generics pasando parámetros

Hice este método:

public static <T> void myMethod(Map<Class<T>, MyInterface<T>> map) { 
} 

parametro utiliza T con el fin de asegurarse de que la clase se utiliza como clave es la misma que la clase se utiliza como parámetro en MyInterface.

Ahora quiero pasar un mapa con las diferentes clases como claves, por supuesto, y las implementaciones correspondientes de MyInterface.

Pero no funciona, obteniendo errores de sintaxis debido a los parámetros de tipo. Aquí está el código, espero que se explique por sí mismo.

import java.util.HashMap; 
    import java.util.Map; 

    public class Test { 

    public static void main(String[] args) { 
     Map<Class<?>, MyInterface<?>> map = new HashMap<Class<?>, MyInterface<?>>(); 

    //  Map<Class<Object>, MyInterface<Object>> map = new HashMap<Class<Object>, MyInterface<Object>>(); 

     map.put(Object.class, new MyObjectImpl()); 

     //if I use Map<Class<Object>, MyInterface<Object>> I get a compiler error here 
     //because map<String> is not map<Object> basically 
     map.put(String.class, new MyStringImpl()); 

     //this would be possible using <?>, which is exactly what I don't want 
    //  map.put(String.class, new MyIntegerImpl()); 

     //<?> generates anyways a compiler error 
     myMethod(map); 
    } 

    //use T to make sure the class used as key is the same as the class of the parameter "object" in doSomething 
    public static <T> void myMethod(Map<Class<T>, MyInterface<T>> map) { 

    } 

    interface MyInterface<T> { 
     void doSomething(T object); 
    } 

    static class MyObjectImpl implements MyInterface<Object> { 
     @Override 
     public void doSomething(Object object) { 
      System.out.println("MyObjectImpl doSomething"); 
     } 
    } 

    static class MyStringImpl implements MyInterface<String> { 
     @Override 
     public void doSomething(String object) { 
      System.out.println("MyStringImpl doSomething"); 
     } 
    } 

    static class MyIntegerImpl implements MyInterface<Integer> { 
     @Override 
     public void doSomething(Integer object) { 
      System.out.println("MyIntegerImpl doSomething"); 
     } 
    } 
} 
+0

nombres de las clases terminan en 'Impl' suelen ser síntomas de exceso de ingeniería (por ejemplo, implica que sólo hay una implementación de una interfaz dada , que no se puede hacer cumplir). – Romain

+1

Olvídalo, esto no puede funcionar. Los genéricos solo pueden definir un mapa homogéneo.No se puede aplicar nada en cada entrada, solo lo mismo para todas las entradas del mapa. –

+0

@Romain, los nombres reales no son así, solo escríbelos para dar un claro ejemplo. Pero tu comentario podría aplicarse también. ¿Qué quiere decir con que implica solo 1 implementación, tengo 3? – Ixx

Respuesta

8

No se puede hacer eso, porque no hay ninguna restricción definida en Map 's put() método entre key y value. Si desea asegurarse de que su mapa se completa correctamente (es decir, crear tal restricción), ocultar el mapa detrás de algunas API que comprobar la corrección, por ejemplo:

public <T> void registerInterface(Class<T> clazz, MyInterface<T> intf) { 
    map.put(clazz, intf); 
} 

Entonces, simplemente llame a la registerInterface en lugar de rellenar manualmente el mapa.

+0

map.put (clazz, intf) Creo :) – Aif

+0

@Aif, gracias - corregido. – npe

0

No creo que esto es posible:

Class<T> solamente nunca acepta T.class como valor. Class<Object> no aceptará String.class, aunque Object es una superclase de String.

Por esta razón, cualquier mapa con Class<T> como clave puede tener un solo elemento, con T.class como valor clave, cualquiera que sea el valor de T.

El compilador solo aceptará un mapa con un valor definido de T como parámetro. No puedes escribir Map<Class<?>, MyInterface<?>> porque cada uno? se supone que es diferente: no coincide con Map<Class<T>, MyInterface<T>> que requiere que T tenga el mismo valor.

Dicho esto, myMethod solo aceptará mapas de entrada única, lo que no parece útil.

0

cambiar su método de firma a

public static <T> void myMethod(Map<Class<? extends T>, MyInterface<? extends T>> map) { 

} 

ahora su declaración y la invocación debería funcionar ..

Map<Class<?>, MyInterface<?>> map = new HashMap<Class<?>, MyInterface<?>>(); 
    map.put(Integer.class, new MyIntegerImpl()); 
    map.put(String.class, new MyStringImpl()); 
    map.put(Object.class, new MyObjectImpl()); 
    myMethod(map); 
+0

Pero este mapa todavía le permite hacer 'map.put (Integer.class, MyStringImpl());' que es exactamente lo que NO se pregunta en la pregunta. Por cierto, el OP ya sugirió eso en su pregunta. –

3

Por lo que yo sé, no puede declarar un Mapa como el que describe en Java. Todo lo que puede hacer es realizar comprobaciones de tipo y/o agregar restricciones.

Guava ofrece algo que se acerca a su problema con ClassToInstanceMap. Así que una manera de hacer esto sería utilizar MapConstraints.constrainedMap (como el ejemplo a continuación)

import java.text.ParseException; 
import java.util.HashMap; 
import java.util.Map; 

import com.google.common.collect.MapConstraint; 
import com.google.common.collect.MapConstraints; 

public class Main { 

    interface MyInterface<T> { 
     void doSomething(T object); 

     Class<T> getType(); 
    } 

    static class MyObjectImpl implements MyInterface<Object> { 
     @Override 
     public void doSomething(Object object) { 
      System.out.println("MyObjectImpl doSomething"); 
     } 

     @Override 
     public Class<Object> getType() { 
      return Object.class; 
     } 
    } 

    static class MyStringImpl implements MyInterface<String> { 
     @Override 
     public void doSomething(String object) { 
      System.out.println("MyStringImpl doSomething"); 
     } 

     @Override 
     public Class<String> getType() { 
      return String.class; 
     } 
    } 

    static class MyIntegerImpl implements MyInterface<Integer> { 
     @Override 
     public void doSomething(Integer object) { 
      System.out.println("MyIntegerImpl doSomething"); 
     } 

     @Override 
     public Class<Integer> getType() { 
      return Integer.class; 
     } 
    } 

    public static void main(String[] args) throws ParseException { 

     Map<Class<?>, MyInterface<?>> map = MapConstraints.constrainedMap(new HashMap<Class<?>, Main.MyInterface<?>>(), 
       new MapConstraint<Class<?>, MyInterface<?>>() { 
        @Override 
        public void checkKeyValue(Class<?> key, MyInterface<?> value) { 
         if (value == null) { 
          throw new NullPointerException("value cannot be null"); 
         } 
         if (value.getType() != key) { 
          throw new IllegalArgumentException("Value is not of the correct type"); 
         } 
        } 
       }); 
     map.put(Integer.class, new MyIntegerImpl()); 
     map.put(String.class, new MyStringImpl()); 
     map.put(Object.class, new MyObjectImpl()); 
     map.put(Float.class, new MyIntegerImpl()); //<-- Here you will get an exception 
    } 
} 
+0

+1 porque es muy informativo, pero la otra respuesta es más genérica, en mi opinión actual. – Ixx