¿Cuándo usar métodos generics y cuándo usar comodines?

Estoy leyendo sobre métodos generics de OracleDocGenericMethod . Estoy bastante confundido acerca de la comparación cuando dice cuándo usar comodines y cuándo usar métodos generics. Citando del documento.

interface Collection { public boolean containsAll(Collection c); public boolean addAll(Collection c); } 

Podríamos haber usado métodos generics aquí en su lugar:

 interface Collection { public  boolean containsAll(Collection c); public  boolean addAll(Collection c); // Hey, type variables can have bounds too! } 

[…] Esto nos dice que el argumento de tipo se está utilizando para polymorphism; su único efecto es permitir que se utilicen una variedad de tipos de argumentos reales en diferentes sitios de invocación. Si ese es el caso, uno debe usar comodines. Los comodines están diseñados para admitir la subtipificación flexible, que es lo que estamos tratando de express aquí.

¿No creemos que el comodín (Collection c); también es compatible con el tipo de polymorphism? Entonces, ¿por qué el uso de métodos generics no se considera bueno en esto?

Continuando adelante, dice,

Los métodos generics permiten que los parámetros de tipo se utilicen para express dependencias entre los tipos de uno o más argumentos de un método y / o su tipo de devolución. Si no existe tal dependencia, no se debe usar un método genérico.

¿Qué significa esto?

Han presentado el ejemplo

 class Collections { public static  void copy(List dest, List src) { ... } 

[…]

Podríamos haber escrito la firma para este método de otra manera, sin usar comodines en absoluto:

 class Collections { public static  void copy(List dest, List src) { ... } 

¿El documento desalienta la segunda statement y promueve el uso de la primera syntax? ¿Cuál es la diferencia entre la primera y la segunda statement? ¿Ambos parecen estar haciendo lo mismo?

¿Alguien puede iluminar esta área?

Hay ciertos lugares, donde los comodines y los parámetros de tipo hacen lo mismo. Pero también hay ciertos lugares donde debes usar parámetros de tipo.

  1. Si desea aplicar alguna relación en los diferentes tipos de argumentos de método, no puede hacer eso con comodines, tiene que usar parámetros de tipo.

Tomando su método como ejemplo, suponga que desea asegurarse de que la lista src y dest pasó al método copy() debe ser del mismo tipo parametrizado, puede hacerlo con parámetros de tipo como ese:

 public static  void copy(List dest, List src) 

Aquí, se asegura que tanto dest como src tengan el mismo tipo parametrizado para List . Por lo tanto, es seguro copiar elementos de src a dest .

Pero, si vas a cambiar el método para usar el comodín:

 public static void copy(List dest, List src) 

no funcionará como se esperaba En el segundo caso, puede pasar List y List como dest y src . Por lo tanto, mover elementos de src a dest ya no sería seguro. Si no necesita ese tipo de relación, entonces es libre de usar parámetros de tipo.

Algunas otras diferencias entre el uso de comodines y los parámetros de tipo son:

  • Si solo tiene un argumento de tipo parametrizado, puede usar comodines, aunque el parámetro de tipo también funcionará.
  • Los parámetros de tipo admiten varios límites, los comodines no.
  • Los comodines admiten los límites superior e inferior, los parámetros de tipo solo admiten límites superiores. Entonces, si quiere definir un método que tome una List de tipo Integer o su súper clase, puede hacer:

     public void print(List list) // OK 

    pero no puedes usar el parámetro de tipo:

      public  void print(List list) // Won't compile 

Referencias

  • Preguntas frecuentes sobre generics Java de Angelika Langer

En su primera pregunta: Significa que si hay una relación entre el tipo de parámetro y el tipo de retorno del método, utilice un genérico.

Por ejemplo:

 public  T giveMeMaximum(Collection items); public  Collection applyFilter(Collection items); 

Aquí está extrayendo algunos de los T siguiendo ciertos criterios. Si T es Long sus métodos devolverán Long y Collection ; el tipo de devolución real depende del tipo de parámetro, por lo que es útil, y aconseja, utilizar tipos generics.

Cuando este no es el caso, puede usar tipos de comodines:

 public int count(Collection items); public boolean containsDuplicate(Collection items); 

En este dos ejemplos cualquiera que sea el tipo de los elementos en las colecciones, los tipos de retorno serán int y boolean .

En tus ejemplos:

 interface Collection { public boolean containsAll(Collection c); public boolean addAll(Collection c); } 

esas dos funciones devolverán un valor booleano cualesquiera que sean los tipos de los elementos en las colecciones. En el segundo caso, está limitado a instancias de una subclase de E.

Segunda pregunta:

 class Collections { public static  void copy(List dest, List src) { ... } 

Este primer código le permite pasar una List src heterogénea List src List src como parámetro. Esta lista puede contener múltiples elementos de diferentes clases, siempre y cuando todos ellos amplíen la clase base T.

si tuvieras:

 interface Fruit{} 

y

 class Apple implements Fruit{} class Pear implements Fruit{} class Tomato implements Fruit{} 

Podrías hacerlo

 List basket = new ArrayList(); basket.add(new Apple()); basket.add(new Pear()); basket.add(new Tomato()); List fridge = new ArrayList(); Collections.copy(fridge, basket);// works 

Por otra parte

 class Collections { public static  void copy(List dest, List src) { ... } 

constriñe List src para que sea de una clase particular S que es una subclase de T. La lista solo puede contener elementos de una clase (en este caso S) y ninguna otra clase, incluso si implementan T también. No podría usar mi ejemplo anterior pero podría hacer:

 List basket = new ArrayList(); basket.add(new Apple()); basket.add(new Apple()); basket.add(new Apple()); List fridge = new ArrayList(); Collections.copy(fridge, basket); /* works since the basket is defined as a List of apples and not a list of some fruits. */ 

Considere seguir el ejemplo de The Java Programming by James Gosling, cuarta edición a continuación donde queremos fusionar 2 SinglyLinkQueue:

 public static  void merge(SinglyLinkQueue d, SinglyLinkQueue s){ // merge s element into d } public static  void merge(SinglyLinkQueue d, SinglyLinkQueue s){ // merge s element into d } 

Ambos métodos anteriores tienen la misma funcionalidad. Entonces, ¿cuál es preferible? La respuesta es la segunda. En las propias palabras del autor:

“La regla general es usar comodines cuando puedas porque el código con comodines es generalmente más legible que el código con múltiples parámetros de tipo. Cuando decidas si necesitas una variable de tipo, pregúntate si esa variable de tipo se usa para relacionar dos o más parámetros, o para relacionar un tipo de parámetro con el tipo de retorno. Si la respuesta es no, entonces un comodín debería ser suficiente “.

Nota: En el libro solo se proporciona el segundo método y el nombre del parámetro de tipo es S en lugar de ‘T’. El primer método no está en el libro.

El método de comodín también es genérico; puede llamarlo con algún rango de tipos.

La syntax define un nombre de variable de tipo. Si una variable de tipo tiene algún uso (por ejemplo, en la implementación del método o como una restricción para otro tipo), entonces tiene sentido nombrarlo, de lo contrario, ¿podría usarlo ? , como variable anónima. Entonces, parece solo un atajo.

Por otra parte, el ? la syntax no se puede evitar cuando declaras un campo:

 class NumberContainer { Set numbers; } 

Trataré de responder tu pregunta, uno por uno.

¿No creemos que el comodín (Collection c); también es compatible con el tipo de polymorphism?

No. La razón es que el comodín delimitado no tiene un tipo de parámetro definido. Es un desconocido. Todo lo que “sabe” es que la “contención” es de un tipo E (cualquiera que sea). Por lo tanto, no puede verificar y justificar si el valor proporcionado coincide con el tipo delimitado.

Por lo tanto, no es sensato tener comportamientos polimórficos en los comodines.

¿El documento desalienta la segunda statement y promueve el uso de la primera syntax? ¿Cuál es la diferencia entre la primera y la segunda statement? ¿Ambos parecen estar haciendo lo mismo?

La primera opción es mejor en este caso ya que T siempre está limitada, y la source definitivamente tendrá valores (de incógnitas) que subclases T

Entonces, supongamos que quiere copiar toda la lista de números, la primera opción será

 Collections.copy(List dest, List src); 

src , esencialmente, puede aceptar List , List , etc. ya que hay un límite superior al tipo parametrizado encontrado en dest .

La segunda opción te obligará a unir S para cada tipo que quieras copiar, como

 //For double Collections.copy(List dest, List src); //Double extends Number. //For int Collections.copy(List dest, List src); //Integer extends Number. 

Como S es un tipo parametrizado que necesita vincularse.

Espero que esto ayude.

Otra diferencia que no se encuentra aquí.

 static  void fromArrayToCollection(T[] a, Collection c) { for (T o : a) { c.add(o); // correct } } 

Pero lo siguiente dará como resultado un error de tiempo de comstackción.

 static  void fromArrayToCollection(T[] a, Collection c) { for (T o : a) { c.add(o); // compile time error } } 

Por lo que yo entiendo, solo hay un caso de uso cuando el comodín es estrictamente necesario (es decir, puede express algo que no se puede express usando parámetros de tipo explícitos). Aquí es cuando necesita especificar un límite inferior.

Aparte de eso, los comodines sirven para escribir un código más conciso, como se describe en las siguientes afirmaciones del documento que usted menciona:

Los métodos generics permiten que los parámetros de tipo se utilicen para express dependencias entre los tipos de uno o más argumentos de un método y / o su tipo de devolución. Si no existe tal dependencia, no se debe usar un método genérico.

[…]

El uso de comodines es más claro y conciso que la statement de parámetros de tipo explícitos, por lo que debe preferirse siempre que sea posible.

[…]

Los comodines también tienen la ventaja de que pueden usarse fuera de las firmas de métodos, como los tipos de campos, variables locales y matrices.