¿Por qué es super.super.method(); ¿No está permitido en Java?
Leí esta pregunta y pensé que se resolvería fácilmente (no es que no se pueda resolver sin ella) si se pudiera escribir:
@Override
public String toString() {
return super.super.toString();
}
No estoy seguro de si es útil en muchos casos, pero me pregunto por qué. no lo es y si existe algo como esto en otros idiomas.
¿Qué piensan ustedes?
EDITAR: Para aclarar: sí, lo sé, eso es imposible en Java y realmente no lo extraño. Esto no es algo que esperaba que funcionara y me sorprendió recibir un error del compilador. Acabo de tener la idea y me gusta discutirla.
Viola la encapsulación. No debería poder pasar por alto el comportamiento de la clase principal. A veces tiene sentido poder evitar el comportamiento de tu propia clase (particularmente dentro del mismo método) pero no el de tus padres. Por ejemplo, supongamos que tenemos una "colección de elementos" base, una subclase que representa "una colección de elementos rojos" y una subclase de la que representa "una colección de elementos rojos grandes". Tiene sentido tener:
public class Items
{
public void add(Item item) { ... }
}
public class RedItems extends Items
{
@Override
public void add(Item item)
{
if (!item.isRed())
{
throw new NotRedItemException();
}
super.add(item);
}
}
public class BigRedItems extends RedItems
{
@Override
public void add(Item item)
{
if (!item.isBig())
{
throw new NotBigItemException();
}
super.add(item);
}
}
Está bien: RedItems siempre puede estar seguro de que todos los elementos que contiene son rojos. Ahora supongamos que pudiéramos llamar a super.super.add():
public class NaughtyItems extends RedItems
{
@Override
public void add(Item item)
{
// I don't care if it's red or not. Take that, RedItems!
super.super.add(item);
}
}
Ahora podemos agregar lo que queramos y el invariante in RedItems
se rompe.
¿Tiene sentido?
Creo que Jon Skeet tiene la respuesta correcta. Solo me gustaría agregar que puedes acceder a variables sombreadas de superclases de superclases mediante la conversión this
:
interface I { int x = 0; }
class T1 implements I { int x = 1; }
class T2 extends T1 { int x = 2; }
class T3 extends T2 {
int x = 3;
void test() {
System.out.println("x=\t\t" + x);
System.out.println("super.x=\t\t" + super.x);
System.out.println("((T2)this).x=\t" + ((T2)this).x);
System.out.println("((T1)this).x=\t" + ((T1)this).x);
System.out.println("((I)this).x=\t" + ((I)this).x);
}
}
class Test {
public static void main(String[] args) {
new T3().test();
}
}
que produce la salida:
x= 3 super.x= 2 ((T2)esto).x= 2 ((T1)esto).x= 1 ((yo)esto).x= 0
(ejemplo del JLS )
Sin embargo, esto no funciona para llamadas a métodos porque las llamadas a métodos se determinan en función del tipo de tiempo de ejecución del objeto.
Creo que el siguiente código permite usar super.super...super.method() en la mayoría de los casos. (incluso si es feo hacer eso)
En breve
- crear una instancia temporal de tipo ancestro
- copiar valores de campos del objeto original al temporal
- invocar el método de destino en un objeto temporal
- copiar los valores modificados al objeto original
Uso:
public class A {
public void doThat() { ... }
}
public class B extends A {
public void doThat() { /* don't call super.doThat() */ }
}
public class C extends B {
public void doThat() {
Magic.exec(A.class, this, "doThat");
}
}
public class Magic {
public static <Type, ChieldType extends Type> void exec(Class<Type> oneSuperType, ChieldType instance,
String methodOfParentToExec) {
try {
Type type = oneSuperType.newInstance();
shareVars(oneSuperType, instance, type);
oneSuperType.getMethod(methodOfParentToExec).invoke(type);
shareVars(oneSuperType, type, instance);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static <Type, SourceType extends Type, TargetType extends Type> void shareVars(Class<Type> clazz,
SourceType source, TargetType target) throws IllegalArgumentException, IllegalAccessException {
Class<?> loop = clazz;
do {
for (Field f : loop.getDeclaredFields()) {
if (!f.isAccessible()) {
f.setAccessible(true);
}
f.set(target, f.get(source));
}
loop = loop.getSuperclass();
} while (loop != Object.class);
}
}
No tengo suficiente reputación para comentar, así que agregaré esto a las otras respuestas.
Jon Skeet responde excelentemente, con un bello ejemplo. Matt B tiene razón: no todas las superclases tienen superclases. Su código se rompería si llamara a un super de un super que no tenía super.
La programación orientada a objetos (que es Java) tiene que ver con objetos, no con funciones. Si desea programación orientada a tareas, elija C++ u otra cosa. Si su objeto no encaja en su superclase, entonces necesita agregarlo a la "clase abuela", crear una nueva clase o encontrar otra superclase en la que encaje.
Personalmente, he descubierto que esta limitación es una de las mayores fortalezas de Java. El código es algo rígido en comparación con otros lenguajes que he usado, pero siempre sé qué esperar. Esto ayuda con el objetivo "simple y familiar" de Java. En mi opinión, llamar a super.super no es sencillo ni familiar. ¿Quizás los desarrolladores sintieron lo mismo?