¿Cuándo usar comodines en Java Generics?

3 minutos de lectura

avatar de usuario
Koray Tugay

esto es de HeadFirst Java: ( página 575 )

Esta:

public <T extends Animal> void takeThing(ArrayList<T> list)

Hace lo mismo que esto:

public void takeThing(ArrayList<? extends Animal> list)

Así que aquí está mi pregunta: si son exactamente iguales, ¿por qué no escribimos

public <? extends Animal> void takeThing(ArrayList<?> list)

o

public void takeThing(ArrayList<T extends Animal> list)

Además, ¿cuándo sería útil usar un ? en lugar de una T en una declaración de método (como arriba) con Genéricos, o para una declaración de Clase? ¿Cuales son los beneficios?

  • Cómo puedes decir public <? extends Animal> void takeThing(ArrayList<?> list)? si dice nulo, entonces no hay tipo de retorno. si especifica, entonces hay un tipo de retorno. No sabía que puede especificar tener un tipo de devolución o ningún tipo de devolución.

    – DevZer0

    23 de mayo de 2013 a las 7:23

  • stackoverflow.com/questions/13474791/… ¡Esto es para referencia futura de quien vea esta pregunta!

    – Koray Tugay

    23 de mayo de 2013 a las 7:46

avatar de usuario
Werzi2001

La gran diferencia entre

public <T extends Animal> void takeThing(ArrayList<T> list)

y

public void takeThing(ArrayList<? extends Animal> list)

es que en el método anterior puede referirse a “T” dentro del método como la clase concreta que se le dio. En el segundo método no puedes hacer esto.

Aquí un ejemplo más complejo para ilustrar esto:

// here i can return the concrete type that was passed in
public <T extends Animal> Map<T, String> getNamesMap(ArrayList<T> list) {
    Map<T, String> names = new HashMap<T, String>();
    for (T animal : list) {
        names.put(animal, animal.getName()); // I assume there is a getName() method
    }
    return names;
}

// here i have to use general Animal
public Map<Animal, String> getNamesMap(ArrayList<? extends Animal> list) {
    Map<Animal, String> names = new HashMap<Animal, String>();
    for (Animal animal : list) {
        names.put(animal, animal.getName()); // I assume there is a getName() method
    }
    return names;
}

Con el primer método, si pasa una Lista de gatos, obtiene un Mapa con Gato como clave. El segundo método siempre devolvería un Mapa con la clave Animal general.

Por cierto, esto no es una sintaxis java válida:

public <? extends Animal> void takeThing(ArrayList<?> list)

Al usar esta forma de declaración de método genérico, debe usar un identificador Java válido y no “?”.

Editar:

La forma “? extiende el tipo” solo se aplica a la declaración de tipo de variable o parámetro. Dentro de una declaración de método genérico, tiene que ser “Tipo de extensión de identificador”, ya que puede referirse al “Identificador” desde su método.

  • La gran diferencia no es que no se puede consultar? como un tipo. ¿Si eso fuera lo único? es bueno para, entonces sería completamente inútil.

    – bennidi

    23 de mayo de 2013 a las 8:13

  • @bennidi: ¿Qué grandes diferencias ves en el caso de una declaración de método? Puede dar los mismos parámetros en ambos casos. La declaración del método genérico le brinda el beneficio de referirse al tipo que permite una mayor seguridad de tipo (que definitivamente no es inútil).

    – Werzi2001

    23 de mayo de 2013 a las 8:21

  • La seguridad de tipo es lo que obtiene cuando usa un identificador y se refiere a eso dentro de su firma de método. No dije que esto fuera inútil. Yo dije eso “?” sería inútil si sus únicas capacidades fueran simplemente no ser referibles en el resto del método. La gran diferencia es que no puedes agregar nada a List. Por favor, vea mi respuesta a continuación.

    – bennidi

    23 de mayo de 2013 a las 8:47

  • Si, eso es correcto. Solo hice este ejemplo para mostrar que T se puede usar dentro del método. Era solo un ejemplo que no estaba directamente relacionado con el ejemplo en la pregunta (ya que pidió diferencias y usos).

    – Werzi2001

    23 mayo 2013 a las 14:20

  • Para el ejemplo en la pregunta no hay necesidad. Pero la pregunta era “Además, ¿cuándo sería útil usar un ? en lugar de una T en una declaración de método (como arriba) con Genéricos, o para una declaración de Clase? ¿Cuáles son los beneficios?” y di un ejemplo.

    – Werzi2001

    24 de mayo de 2013 a las 7:16

avatar de usuario
bennidi

Los comodines se refieren a la variación co/contra de los genéricos. Trataré de aclarar lo que esto significa proporcionando algunos ejemplos.

Básicamente está relacionado con el hecho de que para los tipos S y T, donde S es un subtipo de T, un tipo genérico G<S> no es un subtipo válido de G<T>

List<Number> someNumbers = new ArrayList<Long>(); // compile error

Puedes remediarlo con comodines

List<? extends Number> someNumbers = new ArrayList<Long>(); // this works

Tenga en cuenta que no puede poner nada en esa lista

someNumbers.add(2L); //compile error

incluso (y más sorprendente para muchos desarrolladores):

List<? extends Long> someLongs = new ArrayList<Long>();
someLongs.add(2L); // compile error !!!

Creo que SO no es el lugar adecuado para discutir eso en detalle. Intentaré encontrar algunos de los artículos y documentos que explican esto con más detalle.

  • Por cierto: Ceylon tiene una forma muy agradable de tratar esas especialidades de sistemas tipo.

    – bennidi

    23 de mayo de 2013 a las 8:03

  • la pregunta es sobre los parámetros de un método

    – nueva cuenta

    23 mayo 2013 a las 21:32

  • Esta es la única pregunta en la pizarra que realmente responde la pregunta. Los otros solo dicen que los tipos acotados son superiores a los comodines.

    – rap

    12 de junio de 2014 a las 18:09

  • Gracias @rap. La varianza co y contra no se entiende ampliamente y parece confundir continuamente a las personas (incluyéndome a mí). Creo que el sistema de tipos de Java tampoco ofrece la mejor solución. Ceylon o Scala, por ejemplo, ofrecen muchos más mecanismos para manejar el polimorfismo genérico. Por cierto, vota la respuesta 🙂

    – bennidi

    2 de julio de 2014 a las 7:57

avatar de usuario
Marcos Peters

Vincular el tipo a un parámetro de tipo puede ser más eficaz, dependiendo de lo que se supone que debe hacer el método. no estoy seguro de qué takeThing se supone que debe hacer, pero imagine que, en general, tenemos un método con una de estas firmas de tipo:

public <T extends Animal> void foo(ArrayList<T> list);

//or

public void foo(ArrayList<? extends Animal> list);

He aquí un ejemplo concreto de algo que solo puedes hacer con el primero escriba la firma:

public <T extends Animal> void foo(ArrayList<T> list) {
    list.add(list.remove(0)); // (cycle front element to the back)
} 

En este caso T es necesario informar al verificador de tipos que el elemento que se está remoto de la lista es un elemento OK para agregar a la lista

No podría hacer esto con un comodín porque, dado que el comodín no se ha vinculado a un parámetro de tipo, su contexto no se rastrea (bueno, se rastrea a través de “capturas”, pero no está disponible para aprovechar). Puede obtener más información sobre esto en otra respuesta que he dado: ¿Cómo funcionan los genéricos de los genéricos?

  • “de algo que solo puedes hacer con la primera firma tipográfica” No es cierto. Puedo hacer que un método de la segunda firma llame al método de la primera firma. Luego, a cualquier código externo, “hacen” la misma tarea.

    – nueva cuenta

    23 mayo 2013 a las 21:24


  • @newacct: Ok, eso es un poco quisquilloso. Todavía necesita el parámetro de tipo, solo que no es visible externamente. En la publicación que cito, entro en detalles sobre cómo un método genérico de ayuda puede ayudarlo a “desatar” la captura comodín. Hacer eso en este caso simplemente complica innecesariamente las cosas sin ninguna ventaja perceptible.

    – Mark Peters

    23 mayo 2013 a las 23:13


  • Suponga que está escribiendo una API. Siempre usarías la segunda firma. Si usa internamente otro método con la primera firma es un detalle de implementación que no debe revelarse al exterior. El punto es que, en este caso, el parámetro de tipo no sirve para imponer restricciones a la persona que llama, ya que solo se usa en un lugar en los parámetros. Su propósito pasa a ser completamente interno. Por lo tanto, debe mantenerse interno. Mañana podría cambiar el método para que el parámetro ya no sea necesario; la firma del método no debería tener que cambiar.

    – nueva cuenta

    24 de mayo de 2013 a las 0:52


  • @newacct: No estoy en desacuerdo, pero me está votando negativamente como si la pregunta fuera sobre el mejor enfoque para elegir una firma de API, cuando la pregunta era sobre qué funcionalidad proporciona una sobre la otra. Mi punto principal sigue siendo exactamente correcto. Aunque puede introducir un nivel de direccionamiento indirecto y ocultarlo internamente, aún admite que eventualmente necesitará un método con la primera firma como mencioné. No puede hacer el trabajo con solo la segunda firma.

    – Mark Peters

    24 de mayo de 2013 a las 2:11


Si tú escribes ? extends T dices “cualquier cosa que sea una T o más específica”. Por ejemplo: un List<Shape> solo puede tener Shapes en él, mientras que un List<? extends Shape> puede tener Shapes, Circles, Rectangles, etc

Si tú escribes ? super T dices “cualquier cosa que sea una T o más general”. Esto se usa con menos frecuencia, pero tiene sus casos de uso. Un ejemplo típico sería una devolución de llamada: si desea pasar un Rectangle volver a una devolución de llamada, puede utilizar Callback<? super Rectangle>desde un Callback<Shape> será capaz de manejar Rectangles también.

Aquí está el artículo relevante de Wikipedia.

avatar de usuario
dcernahoschi

Si tu takeThing El método necesita agregar elementos al list parámetro, la versión comodín no se compilará.

El caso interesante es cuando no está agregando a la lista y ambas versiones parecen compilarse y funcionar.

En este caso, escribiría la versión comodín cuando desee permitir diferentes tipos de animales en la lista (más flexibilidad) y la versión de parámetro cuando requiera un tipo fijo de animal en la lista: el tipo T.

por ejemplo el java.util.Collection declara:

interface Collection<E> {
  ...
  public boolean containsAll(Collection<?> c);
  ...
}

Y supongamos que tiene el siguiente código:

Collection<Object> c = Arrays.<Object>asList(1, 2); 
Collection<Integer> i = Arrays.<Integer>asList(1, 2, 3); 
i.containsAll(c); //compiles and return true as expected

Si el java.util.Collection sería:

interface Collection<E> {
    ...
    public boolean containsAll(Collection<E> c);
    ...
}

El código de prueba anterior no compilaría y la flexibilidad del Collection La API se reduciría.

Cabe señalar que la última definición de containsAll tiene la ventaja de detectar más errores en tiempo de compilación, por ejemplo:

Collection<String> c = Arrays.asList("1", "2"); 
Collection<Integer> i = Arrays.asList(1, 2, 3); 
i.containsAll(c); //does not compile, the integer collection can't contain strings

Pero pierde la prueba válida con un Collection<Object> c = Arrays.<Object>asList(1, 2);

  • “Si su método takeThing necesita agregar elementos al parámetro de lista, la versión comodín no se compilará”. Pero, ¿dónde vas a encontrar un T para agregarle de todos modos?

    – nueva cuenta

    23 mayo 2013 a las 21:27

  • @newacct Puede intentar agregar a través de un Animal o Animal referencia de subclase.

    – dcernahoschi

    24 de mayo de 2013 a las 4:58

  • pero entonces no será un T

    – nueva cuenta

    24 de mayo de 2013 a las 9:08

  • Ni siquiera compila. @dcernahoschi

    – Koray Tugay

    8 de julio de 2014 a las 17:02

avatar de usuario
Raul Saini

El uso de Java Generics Wildcards se rige por el principio GET-PUT (que también se conoce como el principio IN-OUT). Esto establece que:
Use un comodín “extendido” cuando solo obtenga valores de una estructura, use un comodín “super” cuando solo coloque valores en una estructura y no use comodines cuando haga ambas cosas. Esto no se aplica al tipo de retorno de un método. No use un comodín como tipo de retorno. Vea el ejemplo a continuación:

public static<T> void copyContainerDataValues(Container<? extends T> source, Container<? super T> destinationtion){
destination.put(source.get());
}

  • “Si su método takeThing necesita agregar elementos al parámetro de lista, la versión comodín no se compilará”. Pero, ¿dónde vas a encontrar un T para agregarle de todos modos?

    – nueva cuenta

    23 mayo 2013 a las 21:27

  • @newacct Puede intentar agregar a través de un Animal o Animal referencia de subclase.

    – dcernahoschi

    24 de mayo de 2013 a las 4:58

  • pero entonces no será un T

    – nueva cuenta

    24 de mayo de 2013 a las 9:08

  • Ni siquiera compila. @dcernahoschi

    – Koray Tugay

    8 de julio de 2014 a las 17:02

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad