java - que - ¿Por qué no puedes tener múltiples interfaces en un comodín delimitado genérico?
java swing tutorial pdf español (5)
Buena pregunta. Me tomó un tiempo darme cuenta.
Vamos a simplificar su caso: está tratando de hacer lo mismo como si declarara una clase que extiende 2 interfaces, y luego una variable que tiene como tipo esas 2 interfaces, algo como esto:
class MyClass implements Int1, Int2 { }
Int1 & Int2 variable = new MyClass()
Por supuesto, ilegal. Y esto es equivalente a lo que intenta hacer con los genéricos. Lo que estás tratando de hacer es:
List<? extends A & B> foobar;
Pero luego, para usar foobar, necesitarías usar una variable de ambas interfaces de esta manera:
A & B element = foobar.get(0);
Lo cual no es legal en Java. Esto significa que está declarando que los elementos de la lista son de 2 tipos simultáneamente, e incluso si nuestros cerebros pueden manejarlo, el lenguaje Java no puede.
Sé que hay todo tipo de propiedades contraintuitivas de los tipos genéricos de Java. Aquí hay uno en particular que no entiendo, y que espero que alguien pueda explicarme. Al especificar un parámetro de tipo para una clase o interfaz, puede vincularlo para que implemente múltiples interfaces con public class Foo<T extends InterfaceA & InterfaceB>
. Sin embargo, si estás instanciando un objeto real, esto ya no funciona. List<? extends InterfaceA>
List<? extends InterfaceA>
está bien, pero List<? extends InterfaceA & InterfaceB>
List<? extends InterfaceA & InterfaceB>
no se puede compilar. Considere el siguiente fragmento completo:
import java.util.List;
public class Test {
static interface A {
public int getSomething();
}
static interface B {
public int getSomethingElse();
}
static class AandB implements A, B {
public int getSomething() { return 1; }
public int getSomethingElse() { return 2; }
}
// Notice the multiple bounds here. This works.
static class AandBList<T extends A & B> {
List<T> list;
public List<T> getList() { return list; }
}
public static void main(String [] args) {
AandBList<AandB> foo = new AandBList<AandB>(); // This works fine!
foo.getList().add(new AandB());
List<? extends A> bar = new LinkedList<AandB>(); // This is fine too
// This last one fails to compile!
List<? extends A & B> foobar = new LinkedList<AandB>();
}
}
Parece que la semántica de la bar
debe estar bien definida. No puedo pensar en ninguna pérdida de seguridad de tipo al permitir una intersección de dos tipos en lugar de solo uno. Estoy seguro de que hay una explicación, sin embargo. ¿Alguien sabe qué es esto?
Curiosamente, la interfaz java.lang.reflect.WildcardType
parece que admite tanto los límites superiores como los inferiores para un comodín arg; y cada uno puede contener límites múltiples
Type[] getUpperBounds();
Type[] getLowerBounds();
Esto es mucho más allá de lo que permite el lenguaje. Hay un comentario oculto en el código fuente
// one or many? Up to language spec; currently only one, but this API
// allows for generalization.
El autor de la interfaz parece considerar que esta es una limitación accidental.
La respuesta enlatada a su pregunta es que los genéricos ya son demasiado complicados; agregar más complejidad podría ser el colmo.
Para permitir que un comodín tenga múltiples límites superiores, uno debe escanear a través de las especificaciones y asegurarse de que todo el sistema siga funcionando.
Un problema que sé estaría en la inferencia de tipo. Las reglas de inferencia actuales simplemente no pueden tratar con tipos de intercepción. No hay regla para reducir una restricción A&B << C
Si lo redujimos a
A<<C
or
A<<B
cualquier motor de inferencia actual tiene que someterse a una revisión general para permitir dicha bifurcación. Pero el verdadero problema serio es que esto permite múltiples soluciones, pero no hay justificación para preferir una sobre otra.
Sin embargo, la inferencia no es esencial para escribir seguridad; simplemente podemos negarnos a inferir en este caso y pedirle al programador que complete explícitamente los argumentos de tipo. Por lo tanto, la dificultad en la inferencia no es un argumento fuerte contra los tipos de intercepción.
De la especificación del lenguaje Java :
4.9 Tipos de intersección Un tipo de intersección toma la forma T1 & ... & Tn, n> 0, donde Ti, 1in, son expresiones de tipo. Los tipos de intersección surgen en los procesos de conversión de captura (§5.1.10) y tipo de inferencia (§15.12.2.7). No es posible escribir un tipo de intersección directamente como parte de un programa; ninguna sintaxis apoya esto . Los valores de un tipo de intersección son aquellos objetos que son valores de todos los tipos Ti, para 1in.
Entonces, ¿por qué esto no es compatible? Mi suposición es, ¿qué deberías hacer con tal cosa? - supongamos que fuera posible:
List<? extends A & B> list = ...
Entonces, ¿qué debería
list.get(0);
¿regreso? No hay sintaxis para capturar un valor de retorno de A & B
Tampoco sería posible agregar algo a esa lista, por lo que es básicamente inútil.
No hay problema ... solo declare el tipo que necesita en la firma del método.
Esto compila:
public static <T extends A & B> void main(String[] args) throws Exception
{
AandBList<AandB> foo = new AandBList<AandB>(); // This works fine!
foo.getList().add(new AandB());
List<? extends A> bar = new LinkedList<AandB>(); // This is fine too
List<T> foobar = new LinkedList<T>(); // This compiles!
}
Por lo que vale: si alguien se lo pregunta porque realmente le gustaría usar esto en la práctica, he trabajado en ello definiendo una interfaz que contiene la unión de todos los métodos en todas las interfaces y clases con las que estoy trabajando. es decir, estaba intentando hacer lo siguiente:
class A {}
interface B {}
List<? extends A & B> list;
que es ilegal, así que en su lugar hice esto:
class A {
<A methods>
}
interface B {
<B methods>
}
interface C {
<A methods>
<B methods>
}
List<C> list;
Esto todavía no es tan útil como poder escribir algo como List<? extends A implements B>
List<? extends A implements B>
, por ejemplo, si alguien agrega o quita métodos a A o B, el tipeo de la lista no se actualizará automáticamente, requiere un cambio manual a C. Pero ha funcionado para mis necesidades.