programacion - Java Generics Puzzler, ampliando una clase y usando comodines
puzzle de 8 piezas java (6)
Está utilizando un comodín acotado (
TbinList<? extends Base>> ...
).
Este comodín le impedirá agregar elementos a la lista.
Si desea más información, aquí está la sección sobre
Wildcards
en los documentos.
He estado golpeándome la cabeza contra este por un tiempo y pensé que quizás algunos ojos nuevos verán el problema; gracias por tu tiempo.
import java.util.*;
class Tbin<T> extends ArrayList<T> {}
class TbinList<T> extends ArrayList<Tbin<T>> {}
class Base {}
class Derived extends Base {}
public class Test {
public static void main(String[] args) {
ArrayList<Tbin<? extends Base>> test = new ArrayList<>();
test.add(new Tbin<Derived>());
TbinList<? extends Base> test2 = new TbinList<>();
test2.add(new Tbin<Derived>());
}
}
Usando Java 8. Me parece que la creación directa del contenedor en
test
es equivalente al contenedor en
test2
, pero el compilador dice:
Test.java:15: error: no suitable method found for add(Tbin<Derived>)
test2.add(new Tbin<Derived>());
^
¿Cómo escribo
Tbin
y
TbinList
para que la última línea sea aceptable?
Tenga en cuenta que en realidad
Tbin
s escritos, por lo que especifiqué
Tbin<Derived>
en la última línea.
Esto sucede debido a la forma en que funciona la conversión de captura :
Existe una conversión de captura de un tipo parametrizado
G<T 1 ,...,T n >
a un tipo parametrizadoG<S 1 ,...,S n >
, donde, para 1 ≤ i ≤ n :
- Si
T i
es un argumento de tipo comodín del formulario? extends B i
? extends B i
, entoncesS i
es una variable de tipo nuevo [...].La conversión de captura no se aplica de forma recursiva.
Tenga en cuenta el bit final. Entonces, lo que esto significa es que, dado un tipo como este:
Map<?, List<?>>
// │ │ └ no capture (not applied recursively)
// │ └ T2 is not a wildcard
// └ T1 is a wildcard
Solo se capturan los comodines "externos".
Se captura el comodín de la clave del
Map
, pero no se utiliza el comodín del elemento
List
.
Es por eso que, por ejemplo, podemos agregar a una
List<List<?>>
, pero no a una
List<?>
.
La ubicación del comodín es lo que importa.
Al llevar esto a
TbinList
, si tenemos una
ArrayList<Tbin<?>>
, el comodín está en un lugar donde no se captura, pero si tenemos una
TbinList<?>
, El comodín está en un lugar donde se obtiene capturado
Como mencioné en los comentarios, una prueba muy interesante es esta:
ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();
Obtenemos este error:
error: incompatible types: cannot infer type arguments for TbinList<>
ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();
^
reason: no instance(s) of type variable(s) T exist so that
TbinList<T> conforms to ArrayList<Tbin<? extends Base>>
Por lo tanto, no hay forma de que funcione como está. Una de las declaraciones de clase necesita ser cambiada.
Además, piénsalo de esta manera.
Supongamos que tenemos:
class Derived1 extends Base {}
class Derived2 extends Base {}
Y dado que un comodín permite el subtipo, podemos hacer esto:
TbinList<? extends Base> test4 = new TbinList<Derived1>();
¿Deberíamos poder agregar un
Tbin<Derived2>
a
test4
?
No, esto sería una gran contaminación.
Podríamos terminar con
Derived2
s flotando en una
TbinList<Derived1>
.
OK, esta es la respuesta:
import java.util.*;
class Tbin<T> extends ArrayList<T> {}
class TbinList<T> extends ArrayList<Tbin<? extends T>> {}
class Base {}
class Derived extends Base {}
public class Test {
public static void main(String[] args) {
TbinList<Base> test3 = new TbinList<>();
test3.add(new Tbin<Derived>());
}
}
Como esperaba, algo obvio una vez que lo vi. Pero muchas sacudidas para llegar hasta aquí. Los genéricos de Java parecen simples si solo observa el código de trabajo.
Gracias a todos por ser una caja de resonancia.
Puede definir los tipos genéricos de la siguiente manera:
class Tbin<T> extends ArrayList<T> {}
class TbinList<K, T extends Tbin<K>> extends ArrayList<T> {}
Entonces crearías una instancia como:
TbinList<? extends Base, Tbin<? extends Base>> test2 = new TbinList<>();
test2.add(new Tbin<Derived>());
Reemplazar la definición de
TbinList
con
class TbinList<T> extends ArrayList<Tbin<? extends T>> {}
y definiendo
test2
con
TbinList<Base> test2 = new TbinList<>();
en cambio resolvería el problema.
Con su definición, está terminando con una
ArrayList<Tbin<T>>
donde T es cualquier clase fija que extiende
Base
.
no puedes agregar ningún objeto a
TbinList<? extends Base>
TbinList<? extends Base>
, no se garantiza qué objetos está insertando en la lista.
Se supone que lee los datos de
test2
cuando usa comodines
extends
Si declaraste como
TbinList<? extends Base>
TbinList<? extends Base>
que significa que es cualquier subclase de la clase Base o de la clase Base, y cuando lo inicializa usa un diamante que no sea el nombre de la clase concreta, hace que su
test2
no sea obvia, lo que hace que sea más difícil saber qué objetos se pueden insertar .
Mi sugerencia es que evite tal declaración, es peligroso, puede que no tenga errores de compilación pero es un código horrible, puede agregar algo, pero también puede agregar algo INCORRECTO que romperá su código.