variable tipos tipo referenciados referencia programacion objetos objeto datos java java-8 type-variables

java - tipos - Error de herencia de valores predeterminados no relacionados para las variables de tipo: ¿por qué?



variables de referencia en java (3)

Descargo de responsabilidad : no se trata de este caso (mientras el error suena igual): la clase hereda los valores predeterminados no relacionados para el separador () de los tipos java.util.Set y java.util.List

y aquí es por qué:

considerar dos interfaces (en el paquete " a ")

interface I1 { default void x() {} } interface I2 { default void x() {} }

Definitivamente me queda claro por qué no podemos declarar tal clase como:

abstract class Bad12 implements I1, I2 { }

(!) Pero no puedo entender esta restricción con referencia a las variables de tipo :

class A<T extends I1&I2> { List<T> makeList() { return new ArrayList<>(); } }

con error: la class java.lang.Object&a.I1&a.I2 inherits unrelated defaults for x() from types a.I1 and a.I2 .

¿Por qué no puedo definir este tipo de variable? ¿Por qué java preocupa por los valores predeterminados no relacionados en este caso? ¿Qué tipo de variable podría "romperse"?

ACTUALIZACIÓN: Sólo para aclarar. Puedo crear varias clases de la forma:

class A1 implements I1, I2 { public void x() { }; } class A2 implements I1, I2 { public void x() { }; }

e incluso

abstract class A0 implements I1, I2 { @Override public abstract void x(); }

y así. ¿Por qué no puedo declarar un tipo especial de variable de tipo para dicho grupo de clases?

UPD-2: Por cierto, no encontré ninguna restricción distinta para este caso en JLS. Sería bueno confirmar su respuesta con referencias a JLS.

UPD-3: Algunos de los usuarios dijeron que este código está bien compilado en Eclipse. No puedo comprobarlo, pero lo comprobé con javac y obtuve este error:

error: class INT#1 inherits unrelated defaults for x() from types I1 and I2 class A<T extends I1&I2> { ^ where INT#1 is an intersection type: INT#1 extends Object,I1,I2 1 error


Esto es simplemente un error. Resulta que el error comienza en la especificación y luego se derrama en la implementación. El error de especificación está aquí: bugs.openjdk.java.net/browse/JDK-7120669

La restricción es perfectamente válida; es claramente posible que existan tipos T que extiendan tanto I1 como I2. El problema es cómo validamos la buena formación de tales tipos.


La mecánica de los "tipos de intersección" (un tipo especificado por la unión de varias interfaces) puede parecer extraña al principio, especialmente cuando se combina con el funcionamiento de los genéricos y el borrado de tipos. Si introduce varios tipos de límites con interfaces que no se extienden entre sí, solo se utilizará el primero como borrado. Si tienes un código como este:

public static <T extends Comparable<T> & Iterable<String>> int f(T t1, T t2) { int res = t1.compareTo(t2); if (res!=0) return res; Iterator<String> s1 = t1.iterator(), s2 = t2.iterator(); // compare the sequences, etc }

Entonces, el bytecode generado no podrá utilizar Iterable en el borrado de T. El borrado real de T sería simplemente comparable, y el bytecode generado contendría conversiones a Iterable según corresponda (emitiendo un checkcast a Iterable además del invokeinterface habitual invokeinterface opcode), que resulta en un código conceptualmente equivalente al siguiente, con la única diferencia de que el compilador también comprueba el límite Iterable<String> iterable:

public static int f(Comparable t1, Comparable t2) { int res = t1.compareTo(t2); if (res!=0) return res; Iterator s1 = ((Iterable)t1).iterator(), s2 = ((Iterable)t2).iterator(); // compare the sequences, etc }

El problema en su ejemplo con métodos de anulación equivalente en las interfaces es que, aunque pueden existir tipos válidos que se ajusten a los límites de tipo solicitados (como se indica en los comentarios), el compilador no puede usar esa verdad de manera significativa debido a presencia de al menos un método por default .

Considere el ejemplo de clase X que implementa tanto I1 como I2 al anular los valores predeterminados con su propio método. Si su tipo de enlace solicitado extends X lugar de extends I1&I2 el compilador lo aceptaría, borrando T a X e insertando invokevirtual Xf() en cada uso de f. Sin embargo, con su tipo enlazado, el compilador borrará T a I1. Dado que el "tipo de unión" X no es real en este segundo caso, en cada uso de tf (), el compilador deberá insertar ya sea invokeinterface I1.f() o invokeinterface I2.f() . Dado que el compilador no puede insertar una llamada a "Xf ()", incluso cuando sabe lógicamente que es posible que un tipo X implemente I1 e I2, y que cualquier X debe declarar esa función, no puede decidir entre las dos interfaces y tiene que rescatar

En el caso específico sin ningún método default , el compilador puede simplemente llamar a cualquiera de las funciones, ya que en ese caso sabe que cualquiera de invokeinterface llamadas invokeinterface se implementará sin ambigüedad en una sola función en cualquier X válida. Sin embargo, cuando los métodos predeterminados ingresen a la imagen, La solución ya no puede suponer que produce un código válido cuando se toma en cuenta la compilación parcial. Considere los siguientes tres archivos:

// A.java public class A { public static interface I1 { void f(); // default int getI() { return 1; } } public static interface I2 { void g(); // default int getI() { return 2; } } } // B.java public class B implements A.I1, A.I2 { public void f() { System.out.println("in B.f"); } public void g() { System.out.println("in B.g"); } } // C.java public class C { public static <T extends A.I1 & A.I2> void test(T var) { var.f(); var.g(); // System.out.println(var.getI()); } public static void main(String[] args) { test(new B()); } }

  • A.java se compila primero, con su código como se muestra, produciendo "v1.0" de las interfaces A.I1 y A.I2
  • B.java se compila a continuación, generando una clase válida (en ese punto) implementando las interfaces
  • Ahora se puede compilar C.java, nuevamente con su código como se muestra, y el compilador lo acepta. Imprime lo que cabría esperar.
  • Los métodos predeterminados en A.java no tienen comentarios y el archivo se vuelve a compilar (produciendo "v.1.1" de las interfaces), pero B.java no se reconstruye en su contra. Esto es similar a la actualización de las bibliotecas principales de JRE, pero no de otra biblioteca que esté utilizando que implemente algunas interfaces de JRE.
  • Finalmente, tratamos de reconstruir C.java porque vamos a utilizar las nuevas y sofisticadas características del último JRE. Independientemente de si descomentamos o no la llamada a getI, el compilador rechaza la declaración de tipo de intersección con el mismo error que se le preguntó.

Si el compilador aceptara el tipo de intersección (A.I1 & A.I2) como válido al crear C.class la segunda vez, correría el riesgo de que las clases existentes como B generen un IncompatibleClassChangeError en el tiempo de ejecución, ya que una llamada a getI en cualquier lugar no se resolvería ni en B ni en Object, y la búsqueda del método predeterminado encontraría dos métodos predeterminados diferentes. El compilador lo protege contra un posible error de tiempo de ejecución al deshabilitar el límite de tipo ofensivo.

Sin embargo, tenga en cuenta que el error todavía puede ocurrir si el límite se reemplaza por T extends B Sin embargo, considero que este último punto es un error del compilador, ya que el compilador ahora puede ver que B implements A.I1, A.I2 con sus métodos predeterminados con firmas equivalentes de anulación, pero no los anula, lo que garantiza un conflicto .

Edición principal : eliminó el primer ejemplo (posiblemente confuso) y agregó una explicación + ejemplo sobre por qué el caso particular con valores predeterminados no está permitido.


Su pregunta es: ¿Por qué no puedo declarar un tipo especial de variable de tipo para dicho grupo de clases?

La respuesta es: porque en su grupo de clases <T extends I1&I2> void x() tiene dos implementaciones predeterminadas. Cualquier implementación concreta de la variable de tipo debe anular esos valores predeterminados.

Su A1 y A2 tienen definiciones diferentes (pero de anulación equivalente) de void x() .

Su A0 es una definición anulada de void x() que reemplaza los valores predeterminados.

class A<T extends I1&I2> { List<T> makeList() { return new ArrayList<>(); } public static void main(String[] args) { // You can''t create an EE to put into A<> which has a default void x() new A<EE>(); } }

JLS 8.4.8.4 Es un error en tiempo de compilación si una clase C hereda un método predeterminado cuya firma es equivalente a la de anulación con otro método heredado por C, a menos que exista un método abstracto declarado en una superclase de C y heredado por C, que es anulación-equivalente con los dos métodos.

JLS 4.4 Una variable de tipo no debe ser al mismo tiempo un subtipo de dos tipos de interfaz que son parametrizaciones diferentes de la misma interfaz genérica, o se produce un error en tiempo de compilación.

JLS 4.9 Cada tipo de intersección T1 & ... & Tninduce una clase teórica o interfaz con el fin de identificar a los miembros del tipo de intersección, de la siguiente manera:

• Para cada Ti (1 ≤ i ≤ n), sea Ci el tipo de matriz o clase más específico tal que Ti <: Ci. Luego debe haber algún Ck tal que Ck <: Ci para cualquier i (1 ≤ i ≤ n), o se produce un error en tiempo de compilación.