tipos - tipo comodin java
¿Cuándo usar métodos genéricos y cuándo usar comodines? (7)
Considere seguir el ejemplo de The Java Programming by James Gosling, cuarta edición a continuación donde queremos fusionar 2 SinglyLinkQueue:
public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){
// merge s element into d
}
public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> 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.
Estoy leyendo sobre métodos genéricos de OracleDocGenericMethod . Estoy bastante confundido acerca de la comparación cuando dice cuándo usar comodines y cuándo usar métodos genéricos. Citando del documento.
interface Collection<E> { public boolean containsAll(Collection<?> c); public boolean addAll(Collection<? extends E> c); }
Podríamos haber usado métodos genéricos aquí en su lugar:
interface Collection<E> { public <T> boolean containsAll(Collection<T> c); public <T extends E> boolean addAll(Collection<T> c); // Hey, type variables can have bounds too! }
[...] Esto nos dice que el argumento de tipo se está utilizando para polimorfismo; 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 expresar aquí.
¿No creemos que el comodín (Collection<? extends E> c);
también es compatible con el tipo de polimorfismo? Entonces, ¿por qué el uso de métodos genéricos no se considera bueno en esto?
Continuando adelante, dice,
Los métodos genéricos permiten que los parámetros de tipo se utilicen para expresar 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 <T> void copy(List<T> dest, List<? extends T> src) { ... }
[...]
Podríamos haber escrito la firma para este método de otra manera, sin usar comodines en absoluto:
class Collections { public static <T, S extends T> void copy(List<T> dest, List<S> src) { ... }
¿El documento desalienta la segunda declaración y promueve el uso de la primera sintaxis? ¿Cuál es la diferencia entre la primera y la segunda declaración? ¿Ambos parecen estar haciendo lo mismo?
¿Alguien puede iluminar esta área?
El método de comodín también es genérico; puede llamarlo con algún rango de tipos.
La sintaxis <T>
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 sintaxis no se puede evitar cuando declaras un campo:
class NumberContainer
{
Set<? extends Number> numbers;
}
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> T giveMeMaximum(Collection<T> items);
public <T> Collection<T> applyFilter(Collection<T> items);
Aquí está extrayendo algunos de los T siguiendo ciertos criterios. Si T es Long
sus métodos devolverán Long
y Collection<Long>
; el tipo de devolución real depende del tipo de parámetro, por lo que es útil, y aconseja, utilizar tipos genéricos.
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<E> {
public boolean containsAll(Collection<?> c);
public boolean addAll(Collection<? extends E> 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 <T> void copy(List<T> dest, List<? extends T> src) {
...
}
Este primer código le permite pasar una List<? extends T> src
heterogénea List<? extends T> src
List<? extends T> 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<? extends Fruit> basket = new ArrayList<? extends Fruit>();
basket.add(new Apple());
basket.add(new Pear());
basket.add(new Tomato());
List<Fruit> fridge = new ArrayList<Fruit>();
Collections.copy(fridge, basket);// works
Por otra parte
class Collections {
public static <T, S extends T> void copy(List<T> dest, List<S> src) {
...
}
constriñe List<S> 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<Apple> basket = new ArrayList<Apple>();
basket.add(new Apple());
basket.add(new Apple());
basket.add(new Apple());
List<Fruit> fridge = new ArrayList<Fruit>();
Collections.copy(fridge, basket); /* works since the basket is defined as a List of apples and not a list of some fruits. */
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.
- 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 <T extends Number> void copy(List<T> dest, List<T> 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<? extends Number> dest, List<? extends Number> src)
no funcionará como se esperaba En el segundo caso, puede pasar List<Integer>
y List<Float>
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 tipoInteger
o su súper clase, puede hacer:public void print(List<? super Integer> list) // OK
pero no puedes usar el parámetro de tipo:
public <T super Integer> void print(List<T> list) // Won''t compile
Referencias
Otra diferencia que no se encuentra aquí.
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o); // correct
}
}
Pero lo siguiente dará como resultado un error de tiempo de compilación.
static <T> 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 expresar algo que no se puede expresar 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 genéricos permiten que los parámetros de tipo se utilicen para expresar 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 declaración 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.
Trataré de responder tu pregunta, uno por uno.
¿No creemos que el comodín
(Collection<? extends E> c);
también es compatible con el tipo de polimorfismo?
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 declaración y promueve el uso de la primera sintaxis? ¿Cuál es la diferencia entre la primera y la segunda declaración? ¿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<Number> dest, List<? extends Number> src);
src
, esencialmente, puede aceptar List<Double>
, List<Float>
, 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<Number> dest, List<Double> src); //Double extends Number.
//For int
Collections.copy(List<Number> dest, List<Integer> src); //Integer extends Number.
Como S
es un tipo parametrizado que necesita vincularse.
Espero que esto ayude.