¿Por qué este código se compila en Java 1.6 pero no en Java 1.7?
generics java-7 (1)
El siguiente código compila bien en Java 1.6 pero no compila en Java 1.7. ¿Por qué?
La parte relevante del código es la referencia al campo ''datos'' privado. La referencia proviene de la misma clase en la que se define el campo y, por lo tanto, parece legal. Pero está sucediendo a través de una variable de tipo genérico. Este código, un ejemplo simplificado basado en una clase de una biblioteca interna, funcionó en Java 1.6 pero ahora no lo hace en Java 1.7.
No estoy preguntando cómo solucionar esto. Ya lo he hecho. Estoy tratando de encontrar una explicación de por qué esto ya no funciona. Tres posibilidades vienen a la mente:
- Este código NO ES LEGAL según el JLS y nunca debería haberse compilado (hubo un error en el compilador 1.6, corregido en 1.7)
- Este código es LEGAL según el JLS y debe compilarse (se ha introducido un error de compatibilidad hacia atrás en el compilador 1.7)
- Este código cae en un AREA GRIS en el JLS
Foo.java:
import java.util.TreeMap;
import java.util.Map;
public abstract class Foo<V extends Foo<V>> {
private final Map<String,Object> data = new TreeMap<String,Object>();
protected Foo() { ; }
// Subclasses should implement this as ''return this;''
public abstract V getThis();
// Subclasses should implement this as ''return new SubclassOfFoo();''
public abstract V getEmpty();
// ... more methods here ...
public V copy() {
V x = getEmpty();
x.data.clear(); // Won''t compile in Java 1.7
x.data.putAll(data); // "
return x;
}
}
Salida del compilador:
> c:/tools/jdk1.6.0_11/bin/javac -version
javac 1.6.0_11
> c:/tools/jdk1.6.0_11/bin/javac c:/temp/Foo.java
> c:/tools/jdk1.7.0_10/bin/javac -version
javac 1.7.0_10
> c:/tools/jdk1.7.0_10/bin/javac c:/temp/Foo.java
Foo.java:18: error: data has private access in Foo
x.data.clear();
^
Foo.java:19: error: data has private access in Foo
x.data.putAll(data);
^
2 errors
Apéndice. El mismo problema ocurre si la referencia es a un método privado en lugar de a una variable miembro privada. Esto funciona en Java 1.6 pero no en 1.7.
Foo2.java:
import java.util.TreeMap;
import java.util.Map;
public abstract class Foo2<V extends Foo2<V>> {
private final Map<String,Object> data = new TreeMap<String,Object>();
protected Foo2() { ; }
// Subclasses should implement this as ''return this;''
public abstract V getThis();
// Subclasses should implement this as ''return new SubclassOfFoo();''
public abstract V getEmpty();
// ... more methods here ...
public V copy() {
V x = getEmpty();
x.theData().clear(); // Won''t compile in Java 1.7
x.theData().putAll(data); // "
return x;
}
private Map<String,Object> theData() {
return data;
}
}
Salida del compilador:
> c:/tools/jdk1.6.0_11/bin/javac c:/temp/Foo2.java
> c:/tools/jdk1.7.0_10/bin/javac c:/temp/Foo2.java
Foo2.java:18: error: theData() has private access in Foo2
x.theData().clear();
^
Foo2.java:19: error: theData() has private access in Foo2
x.theData().putAll(data);
^
El problema demostrado parece coincidir con el comportamiento reportado en el error 6904536 de Oracle . El error se cerró como "No es un problema" con la siguiente explicación:
javac se está comportando de acuerdo con el JLS. Consulte también 6558551 , 6711619 y el problema 6644562 JLS relacionado.
El problema JLS correspondiente no está resuelto, con el siguiente comentario:
Una explicación simplificada para la pertenencia de las variables de tipo es bienvenida. Hay una dificultad general con los miembros privados de los límites de una variable de tipo. Formalmente, dichos miembros no se convierten en miembros de la variable de tipo en sí, aunque javac y Eclipse tradicionalmente los hicieron miembros y el código ha llegado a confiar en eso:
class Test { private int count = 0; <Z extends Test> void m(Z z) { count = z.count; // Legal in javac 1.6, illegal in javac 1.7 due to fix for 6711619 } }
Pedro presentó una prueba similar:
class A { static class B { private String f; } abstract static class Builder<T extends B> { abstract T getB(); { ((B)getB()).f.hashCode(); getB().f.hashCode(); // error: f has private access in A.B } } }
Dado que los tipos de intersección se construyen por herencia, y los miembros privados nunca se heredan, es difícil volver a especificar los tipos de intersección para tener miembros privados. No obstante, sería lo compatible.
Para referencia, la sección relevante del JLS es §4.4 .
EDITAR:
Tiendo a estar de acuerdo con el JLS aquí en realidad, porque coincide con sí mismo cuando eliminamos los genéricos de la imagen. Considera este ejemplo:
static class Parent {
private int i;
void m(Child child) {
i = child.i; //compile error
}
}
static class Child extends Parent { }
child.i
no está visible porque el acceso a miembros privados no se hereda. Este punto es llevado a casa por el hecho de que Child
puede tener su propia i
sin ningún tipo de sombra:
static class Child extends Parent {
private int i; //totally fine
}
Así que este sería un raro ejemplo de la necesidad de realizar una conversión ascendente :
void m(Child child) {
i = ((Parent)child).i;
}
Entonces, con la accesibilidad heredada fuera de la imagen, el JLS parece correcto aquí, dado que la V
en Foo<V extends Foo<V>>
no es necesariamente Foo<V>
pero podría ser algún tipo que extienda Foo<V>
.