¿Por qué es super.super.method (); no permitido en Java?


360

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 así 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 nada que esperaba que funcionara y me sorprendió recibir un error del compilador. Acabo de tener la idea y me gusta discutirla.


77
Querer llamar super.super.toString()contradice su propia decisión cuando elige extender una clase y, por lo tanto, acepta todas (no algunas) sus características.
DayaMoon

Respuestas:


484

Viola la encapsulación. No debería poder evitar el comportamiento de la clase principal. A veces tiene sentido poder omitir el comportamiento de su propia clase (particularmente desde el mismo método) pero no el de sus padres. Por ejemplo, supongamos que tenemos una "colección de artículos" base, una subclase que representa "una colección de artículos rojos" y una subclase de esa que representa "una colección de artículos 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 los elementos que contiene son todos rojos. Ahora supongamos que éramos capaces de llamar 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 podríamos agregar lo que queramos, y la invariante RedItemsestá rota.

¿Tiene sentido?


38
buen ejemplo pero pensé que era un mal diseño cuando la clase base acepta elementos, pero la clase derivada los rechaza, porque el derivado no puede usarse como un reemplazo directo para la clase base (violación del principio de sustitución). ¿Es correcto pensar o esta jerarquía es limpia?
Johannes Schaub - litb

55
¿Te refieres a la composición sobre la herencia? Este ejemplo no es una razón para evitar la herencia, aunque hay muchos otros.
Jon Skeet

3
@Tim: Este fue solo un ejemplo fácil de entender de violar la encapsulación. Podría estar estableciendo una propiedad en su lugar. Además, no todos los aspectos serán visibles a nivel de tipo. Los genéricos no son la respuesta a todo.
Jon Skeet

12
@ JohannesSchaub-litb Creo que viola el principio de sustitución de Liskov, ya que si uno codifica el contrato del Artículo y usa una instancia de RedItems, se obtendrá una excepción NotRedItemException inesperada. Siempre me enseñaron que una subclase debería tomar un superconjunto de entrada y devolver un subconjunto de salida. En otras palabras, una subclase nunca debe rechazar una entrada que sea válida para la superclase o producir una salida que la superclase no pueda producir, esto incluye arrojar un error que la superclase no arroja.
Konstantin Tarashchanskiy

44
@piechuckerr: A veces libros, a veces publicaciones de blog, a veces solo experiencia ...
Jon Skeet

72

Creo que Jon Skeet tiene la respuesta correcta. Solo me gustaría agregar que puede acceder a las variables sombreadas de las superclases de las superclases al emitir 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
((I) esto) .x = 0

(ejemplo del JLS )

Sin embargo, esto no funciona para las llamadas a métodos porque las llamadas a métodos se determinan en función del tipo de tiempo de ejecución del objeto.


66
Si tiene una variable con el mismo nombre que una en una superclase y por alguna razón no puede (o no puede) cambiarla, aún puede acceder a la variable de la superclase con el mismo nombre. ¿Cuál es el uso, preguntas? Bueno ... nunca lo he usado.
Michael Myers

Gran enlace al documento de expresiones. Algunos buenos ejemplos para personas que estudian para el OCPJP.
Gordon

Increíble. Nunca hubiera pensado en usar un yeso.
Thomas Eding

¿Cómo es que la clase T1 anula la variable x? su final estático por defecto ¿verdad?
amarnath harish

@amarnathharish: no se anula, y los campos están protegidos por paquetes de forma no final de forma predeterminada.
Michael Myers

41

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

  1. crear una instancia temporal de tipo ancestro
  2. copiar valores de campos del objeto original a uno temporal
  3. invocar método de destino en objeto temporal
  4. copiar valores modificados de nuevo 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);
    }
}

10
Con la reflexión puedes hacer cualquier cosa, sí :) Incluso puedes hacer que las cadenas sean mutables.
BalusC,

23
¡Qué cosa tan horrible de hacer! Te doy +1 solo por descubrir cómo hacerlo :)
Larry Watanabe

66
Es un buen truco, pero incluso eso no siempre es equivalente a llamar a super.super no disponible, pero sí necesario, y eso se debe a que la llamada super.super llevaría el contexto de C (C + B + A) mientras que sus respuestas crean una instancia de A sin el contexto de B y C. Por lo tanto, esta respuesta no funcionará si cada doThat llamado getContext (), por ejemplo, y getContext se implementó de manera diferente en cada clase. en su respuesta, usaría getContext () de A mientras que llamar al super.super no disponible habría resultado en usar getContext de C.
desde el

Hmm En algunos casos, ¿tal vez podría superar la objeción de Inor con proxies dinámicos ( javahowto.blogspot.co.uk/2011/12/… ), redirigiendo las llamadas al método al objeto original (sincronizando variables después de cada llamada)? Sin embargo, parece que los servidores proxy requieren que todo esté implementando una interfaz. Además, me pregunto si es posible que la clase súper súper llame a uno de sus métodos súper súper, específicamente, y no necesite redirigir esos ...
Erhannis

Dije en otro comentario que el bloqueo super.super.invita a los programadores a encontrar formas nuevas, enrevesadas y atroces de dispararse en el pie en busca de una solución alternativa, este es un ejemplo perfecto de eso, porque sus colegas probablemente lo odiarán tanto por escribir algo de esta manera, te dispararán personal y literalmente en el pie. +1
Braden Mejor

11

No tengo suficiente reputación para comentar, así que agregaré esto a las otras respuestas.

Jon Skeet responde excelentemente, con un hermoso ejemplo. Matt B tiene un punto: no todas las superclases tienen supers. 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) se trata de objetos, no de funciones. Si desea una programación orientada a tareas, elija C ++ u otra cosa. Si su objeto no encaja en su superclase, entonces debe agregarlo a la "clase de abuelo", crear una nueva clase o encontrar otro súper en el que encaje.

Personalmente, he encontrado que esta limitación es una de las mayores fortalezas de Java. El código es algo rígido en comparación con otros idiomas 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 simple ni familiar. ¿Quizás los desarrolladores sintieron lo mismo?


3
Dices "no todas las superclases tienen superestrellas". Bueno, todos menos java.lang.Object lo que podría darte "nulo". Entonces diría que casi todos tienen súper.
Tim Büthe

Cada clase que escribe el programador de la aplicación tiene un "super" (java.lang.Object no lo hace, pero el programador de la aplicación no escribe eso.)
finnw

2
Se resuelve fácilmente haciendo super.super.super ... super un error de tiempo de compilación si hay demasiados supers. Teniendo en cuenta que Java solo tiene herencia pública, si alguien cambió la jerarquía de herencia, está cambiando la interfaz. Así que no me preocuparía que super ^ n da miedo.
Thomas Eding

7

Hay algunas buenas razones para hacer esto. Es posible que tenga una subclase que tiene un método que se implementa incorrectamente, pero el método principal se implementa correctamente. Debido a que pertenece a una biblioteca de terceros, es posible que no pueda / no quiera cambiar la fuente. En este caso, desea crear una subclase pero anular un método para llamar al método super.super.

Como se muestra en algunos otros carteles, es posible hacer esto a través de la reflexión, pero debería ser posible hacer algo como

(SuperSuperClass this) .theMethod ();

Estoy lidiando con este problema en este momento: la solución rápida es copiar y pegar el método de superclase en el método de subclase :)


1
Lanzar antes de llamar a un método no cambia el método al que se delega: siempre se usa la implementación de la subclase, nunca la súper. Ver, por ejemplo, stackoverflow.com/questions/1677993/…
Joshua Goldberg el

1
@Larry esta es exactamente la situación en la que estaba, y exactamente la solución que usé. ¡Buena llamada!
bcr

Si tiene el código del método necesario, puede abrir todos los campos necesarios y ejecutar este código en su subclase.
Enyby

6

Además de los muy buenos puntos que otros han hecho, creo que hay otra razón: ¿qué pasa si la superclase no tiene una superclase?

Como cada clase se extiende naturalmente (al menos) Object, super.whatever()siempre se referirá a un método en la superclase. Pero lo que si su clase sólo se extiende Object- lo que super.superse refieren a continuación? ¿Cómo debe manejarse ese comportamiento: un error del compilador, un puntero nulo, etc.?

Creo que la razón principal por la que esto no está permitido es que viola la encapsulación, pero esta podría ser una pequeña razón también.


44
Obviamente, sería un error del compilador, y es información que el compilador sí tiene.
Michael Borgwardt

4

Creo que si sobrescribe un método y desea toda la versión de equalssuperclase del mismo (como, por ejemplo ), entonces virtualmente siempre querrá llamar primero a la versión de superclase directa, que a su vez llamará a su versión de superclase si así lo desea .

Creo que rara vez tiene sentido (si es que no. No puedo pensar en un caso en el que lo haga) llamar a una versión arbitraria de una superclase de un método. No sé si eso es posible en Java. Se puede hacer en C ++:

this->ReallyTheBase::foo();

66
Tiene sentido si alguien escribió una subclase con un método implementado incorrectamente pero el método de superclase realiza el 90% del trabajo. Luego, desea hacer una subclase, anular el método que llama al método superclase superclase y agregar su propio 10%.
Larry Watanabe

3

Supongo que porque no se usa con tanta frecuencia. La única razón por la que pude ver su uso es si su padre directo ha anulado alguna funcionalidad y está tratando de restaurarlo al original.

Lo que me parece estar en contra de los principios OO, ya que el padre directo de la clase debería estar más estrechamente relacionado con su clase que el abuelo.


3

Mire este proyecto de Github, especialmente la variable objectHandle. Este proyecto muestra cómo llamar de manera real y precisa al método de abuelo en un nieto.

En caso de que el enlace se rompa, aquí está el código:

import lombok.val;
import org.junit.Assert;
import org.junit.Test;

import java.lang.invoke.*;

/*
Your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should.
Please don't actually do this... :P
*/
public class ImplLookupTest {
    private MethodHandles.Lookup getImplLookup() throws NoSuchFieldException, IllegalAccessException {
        val field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
        field.setAccessible(true);
        return (MethodHandles.Lookup) field.get(null);
    }

    @Test
    public void test() throws Throwable {
        val lookup = getImplLookup();
        val baseHandle = lookup.findSpecial(Base.class, "toString",
            MethodType.methodType(String.class),
            Sub.class);
        val objectHandle = lookup.findSpecial(Object.class, "toString",
            MethodType.methodType(String.class),
            // Must use Base.class here for this reference to call Object's toString
            Base.class);
        val sub = new Sub();
        Assert.assertEquals("Sub", sub.toString());
        Assert.assertEquals("Base", baseHandle.invoke(sub));
        Assert.assertEquals(toString(sub), objectHandle.invoke(sub));
    }

    private static String toString(Object o) {
        return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode());
    }

    public class Sub extends Base {
        @Override
        public String toString() {
            return "Sub";
        }
    }

    public class Base {
        @Override
        public String toString() {
            return "Base";
        }
    }
}

Feliz codificación !!!!


Sus científicos estaban tan preocupados por si podían o no, que no se detuvieron a pensar si debían hacerlo. Por favor, no hagas esto ...: P 🤔
Tim Büthe

Sí, esas son las palabras del codificador, no las mías. En mi opinión, creo que es posible que en realidad se necesite algún día
kyay

2

Pondría el cuerpo del método super.super en otro método, si es posible

class SuperSuperClass {
    public String toString() {
        return DescribeMe();
    }

    protected String DescribeMe() {
        return "I am super super";
    }
}

class SuperClass extends SuperSuperClass {
    public String toString() {
        return "I am super";
    }
}

class ChildClass extends SuperClass {
    public String toString() {
        return DescribeMe();
    }
}

O si no puede cambiar la super-super clase, puede intentar esto:

class SuperSuperClass {
    public String toString() {
        return "I am super super";
    }
}

class SuperClass extends SuperSuperClass {
    public String toString() {
        return DescribeMe(super.toString());
    }

    protected String DescribeMe(string fromSuper) {
        return "I am super";
    }
}

class ChildClass extends SuperClass {
    protected String DescribeMe(string fromSuper) {
        return fromSuper;
    }
}

En ambos casos, el

new ChildClass().toString();

resultados para "Soy super super"


Acabo de encontrarme en una situación en la que soy dueño de SuperSuperClass y ChildClass pero no de SuperClass, así que encontré la primera solución útil.
xofon


1
public class A {

     @Override
     public String toString() {
          return "A";
     }

}


public class B extends A {

     @Override
     public String toString() {
          return "B";
     }

}

public class C extends B {

     @Override
     public String toString() {
          return "C";
     }

}


public class D extends C {

     @Override
     public String toString() {
          String result = "";
          try {
                result = this.getClass().getSuperclass().getSuperclass().getSuperclass().newInstance().toString();
          } catch (InstantiationException ex) {
                Logger.getLogger(D.class.getName()).log(Level.SEVERE, null, ex);
          } catch (IllegalAccessException ex) {
                Logger.getLogger(D.class.getName()).log(Level.SEVERE, null, ex);
          }
          return result;
     }

}

public class Main {

     public static void main(String... args) {
          D d = new D();
          System.out.println(d);

     }
}

ejecutar: UNA CREACIÓN EXITOSA (tiempo total: 0 segundos)


1
Ya veo, pero está creando una nueva instancia para que esto no funcione si el objeto tiene algún estado.
Tim Büthe

1

Llamar a super.super.method () tiene sentido cuando no puede cambiar el código de la clase base. Esto sucede a menudo cuando está extendiendo una biblioteca existente.

Pregúntese primero, ¿por qué está extendiendo esa clase? Si la respuesta es "porque no puedo cambiarlo", puede crear un paquete y una clase exactos en su aplicación, y reescribir un método travieso o crear un delegado:

package com.company.application;

public class OneYouWantExtend extends OneThatContainsDesiredMethod {

    // one way is to rewrite method() to call super.method() only or 
    // to doStuff() and then call super.method()

    public void method() {
        if (isDoStuff()) {
            // do stuff
        }
        super.method();
    }

    protected abstract boolean isDoStuff();


    // second way is to define methodDelegate() that will call hidden super.method()

    public void methodDelegate() {
        super.method();
    }
    ...
}

public class OneThatContainsDesiredMethod {

    public void method() {...}
    ...
}

Por ejemplo, puede crear la clase org.springframework.test.context.junit4.SpringJUnit4ClassRunner en su aplicación para que esta clase se cargue antes que la real desde jar. Luego reescribe métodos o constructores.

Atención: este es un truco absoluto, y NO se recomienda su uso, ¡pero FUNCIONA! El uso de este enfoque es peligroso debido a posibles problemas con los cargadores de clases. Además, esto puede causar problemas cada vez que actualice la biblioteca que contiene una clase sobrescrita.


1

He tenido situaciones como estas cuando la arquitectura es crear una funcionalidad común en una CustomBaseClass común que implementa en nombre de varias clases derivadas. Sin embargo, debemos evitar la lógica común para un método específico para una clase derivada específica. En tales casos, debemos usar una implementación super.super.methodX.

Logramos esto mediante la introducción de un miembro booleano en CustomBaseClass, que se puede usar para diferir selectivamente la implementación personalizada y ceder a la implementación de marco predeterminada cuando sea deseable.

        ...
        FrameworkBaseClass (....) extends...
        {
           methodA(...){...}
           methodB(...){...}
        ...
           methodX(...)
        ...
           methodN(...){...}

        }
        /* CustomBaseClass overrides default framework functionality for benefit of several derived classes.*/
        CustomBaseClass(...) extends FrameworkBaseClass 
        {
        private boolean skipMethodX=false; 
        /* implement accessors isSkipMethodX() and setSkipMethodX(boolean)*/

           methodA(...){...}
           methodB(...){...}
        ...
           methodN(...){...}

           methodX(...){
                  if (isSkipMethodX()) {
                       setSKipMethodX(false);
                       super.methodX(...);
                       return;
                       }
                   ... //common method logic
            }
        }

        DerivedClass1(...) extends CustomBaseClass
        DerivedClass2(...) extends CustomBaseClass 
        ...
        DerivedClassN(...) extends CustomBaseClass...

        DerivedClassX(...) extends CustomBaseClass...
        {
           methodX(...){
                  super.setSKipMethodX(true);
                  super.methodX(...);
                       }
        }

Sin embargo, con buenos principios de arquitectura seguidos tanto en el marco como en la aplicación, podríamos evitar tales situaciones fácilmente, usando el enfoque hasA, en lugar del enfoque isA. Pero en todo momento no es muy práctico esperar una arquitectura bien diseñada y, por lo tanto, la necesidad de alejarse de principios de diseño sólidos e introducir hacks como este. Solo mis 2 centavos ...


1

@ Jon Skeet Buena explicación. En mi opinión, si alguien quiere llamar al método super.super, entonces uno debe querer ignorar el comportamiento del padre inmediato, pero quiere acceder al comportamiento del padre principal. Esto se puede lograr a través de la instancia de. Como abajo código

public class A {
    protected void printClass() {
        System.out.println("In A Class");
    }
}

public class B extends A {

    @Override
    protected void printClass() {
        if (!(this instanceof C)) {
            System.out.println("In B Class");
        }
        super.printClass();
    }
}

public class C extends B {
    @Override
    protected void printClass() {
        System.out.println("In C Class");
        super.printClass();
    }
}

Aquí está la clase de conductor,

public class Driver {
    public static void main(String[] args) {
        C c = new C();
        c.printClass();
    }
}

La salida de esto será

In C Class
In A Class

El comportamiento de clase B printClass se ignorará en este caso. No estoy seguro de si es una práctica ideal o buena para lograr super.super, pero aún funciona.


1
Bueno, eso es creativo, pero en realidad no responde a mi pregunta. C todavía no llama super.super, B solo se comporta de manera diferente. Si puede cambiar A y B, simplemente podría agregar otro método en lugar de usar instanceof. Super.super.foo lo ayudaría en casos en los que no tiene acceso a A y B y no puede cambiarlos.
Tim Büthe

De acuerdo @TimButhe, pero si uno quiere llamar a super.super, entonces intencionalmente quiere ignorar el comportamiento de la clase padre, por lo que solo necesita lograrlo mediante la sintaxis existente de java. (Cualquiera de las opciones que desee, ya sea instancia / método diferente)
Sanjay Jain

0

Si cree que va a necesitar la superclase, puede hacer referencia a ella en una variable para esa clase. Por ejemplo:

public class Foo
{
  public int getNumber()
  {
    return 0;
  }
}

public class SuperFoo extends Foo
{
  public static Foo superClass = new Foo();
  public int getNumber()
  {
    return 1;
  }
}

public class UltraFoo extends Foo
{
  public static void main(String[] args)
  {
    System.out.println(new UltraFoo.getNumber());
    System.out.println(new SuperFoo().getNumber());
    System.out.println(new SuperFoo().superClass.getNumber());
  }
  public int getNumber()
  {
    return 2;
  }
}

Debe imprimir:

2
1
0

2
Su ejemplo es un poco ... bueno, porque usa métodos estáticos. Al usar métodos estáticos, no necesita la variable o super en absoluto. Tal vez te perdiste algún concepto básico de OO aquí, asegúrate de leer sobre eso. Tengo que votar abajo, lo siento.
Tim Büthe

1
Podría haberse hecho fácilmente sin métodos estáticos. Es lo suficientemente simple. Estaba buscando un ejemplo simple sobre cómo hacer esto.
Ashtheking

Entiendo su punto, almacenar la súper variable en un campo es una forma de resolver esto. Pero, no necesita la variable si es un método estático, simplemente puede usarla. En segundo lugar, invocar métodos estáticos una variable es una mala práctica y la mayoría de los IDE le advertirán sobre eso. Si corrige su respuesta, eliminando las cosas estáticas, me complacería eliminar mi voto negativo, sin ofender.
Tim Büthe

Estás cerca, pero tu código no se compilará. Intenta acceder a getNumber desde un contexto estático en su método principal. ¿Realmente trataste de compilar esto? (¿y no debería su UltraFoo extender SuperFoo?)
Tim Büthe

Realmente no quiero ser cruel contigo, pero new UltraFoo.getNumber()no compilaré, ya que te perdiste los paréntesis. Sin embargo, acabo de eliminar mi donvote, ya que el concepto de su código es bastante claro ahora, ¡gracias!
Tim Büthe

0

En mi opinión, es una forma limpia de lograr un super.super.sayYourName()comportamiento en Java.

public class GrandMa {  
    public void sayYourName(){  
        System.out.println("Grandma Fedora");  
    }  
}  

public class Mama extends GrandMa {  
    public void sayYourName(boolean lie){  
        if(lie){   
            super.sayYourName();  
        }else {  
            System.out.println("Mama Stephanida");  
        }  
    }  
}  

public class Daughter extends Mama {  
    public void sayYourName(boolean lie){  
        if(lie){   
            super.sayYourName(lie);  
        }else {  
            System.out.println("Little girl Masha");  
        }  
    }  
}  

public class TestDaughter {
    public static void main(String[] args){
        Daughter d = new Daughter();

        System.out.print("Request to lie: d.sayYourName(true) returns ");
        d.sayYourName(true);
        System.out.print("Request not to lie: d.sayYourName(false) returns ");
        d.sayYourName(false);
    }
}

Salida:

Request to lie: d.sayYourName(true) returns Grandma Fedora
Request not to lie: d.sayYourName(false) returns Little girl Masha


ah, ¿estás abogando por implementar una jerarquía de clases como esta? Desafortunadamente, esto comienza a ponerse realmente complicado si desea acceder al método en Mama from Baby (subclase de la hija) ...
bcr

Yakov Fain tiene razón. en otras palabras, no es un buen ejemplo, porque la pregunta original era sobre llamar a un método anulado en super.super.
desde el

0

Creo que este es un problema que rompe el acuerdo de herencia.
Al extender una clase, obedece / acuerda su comportamiento, características
Mientras que cuando llama super.super.method(), quiere romper su propio acuerdo de obediencia.

Simplemente no puedes elegir entre la súper clase .

Sin embargo, pueden ocurrir situaciones en las que sienta la necesidad de llamar super.super.method(), ¡generalmente un signo de diseño incorrecto, en su código o en el código que hereda!
Si las clases super y super super no se pueden refactorizar (algún código heredado), entonces opte por la composición sobre la herencia.

La ruptura de la encapsulación se produce cuando se omiten algunos métodos rompiendo el código encapsulado. Los métodos diseñados para no ser anulados se marcan como finales .


0

En C # puede llamar a un método de cualquier antepasado como este:

public class A
    internal virtual void foo()
...
public class B : A
    public new void foo()
...
public class C : B
    public new void foo() {
       (this as A).foo();
    }

También puedes hacer esto en Delphi:

type
   A=class
      procedure foo;
      ...
   B=class(A)
     procedure foo; override;
     ...
   C=class(B)
     procedure foo; override;
     ...
A(objC).foo();

Pero en Java puedes hacer ese enfoque solo con algún equipo. Una forma posible es:

class A {               
   int y=10;            

   void foo(Class X) throws Exception {  
      if(X!=A.class)
         throw new Exception("Incorrect parameter of "+this.getClass().getName()+".foo("+X.getName()+")");
      y++;
      System.out.printf("A.foo(%s): y=%d\n",X.getName(),y);
   }
   void foo() throws Exception { 
      System.out.printf("A.foo()\n");
      this.foo(this.getClass()); 
   }
}

class B extends A {     
   int y=20;            

   @Override
   void foo(Class X) throws Exception { 
      if(X==B.class) { 
         y++; 
         System.out.printf("B.foo(%s): y=%d\n",X.getName(),y);
      } else { 
         System.out.printf("B.foo(%s) calls B.super.foo(%s)\n",X.getName(),X.getName());
         super.foo(X);
      } 
   }
}

class C extends B {     
   int y=30;            

   @Override
   void foo(Class X) throws Exception { 
      if(X==C.class) { 
         y++; 
         System.out.printf("C.foo(%s): y=%d\n",X.getName(),y);
      } else { 
         System.out.printf("C.foo(%s) calls C.super.foo(%s)\n",X.getName(),X.getName());
         super.foo(X);
      } 
   }

   void DoIt() {
      try {
         System.out.printf("DoIt: foo():\n");
         foo();         
         Show();

         System.out.printf("DoIt: foo(B):\n");
         foo(B.class);  
         Show();

         System.out.printf("DoIt: foo(A):\n");
         foo(A.class);  
         Show();
      } catch(Exception e) {
         //...
      }
   }

   void Show() {
      System.out.printf("Show: A.y=%d, B.y=%d, C.y=%d\n\n", ((A)this).y, ((B)this).y, ((C)this).y);
   }
} 

Resultado del resultado objC.DoIt ():

DoIt: foo():
A.foo()
C.foo(C): y=31
Show: A.y=10, B.y=20, C.y=31

DoIt: foo(B):
C.foo(B) calls C.super.foo(B)
B.foo(B): y=21
Show: A.y=10, B.y=21, C.y=31

DoIt: foo(A):
C.foo(A) calls C.super.foo(A)
B.foo(A) calls B.super.foo(A)
A.foo(A): y=11
Show: A.y=11, B.y=21, C.y=31

En C # solo funcionará para métodos no virtuales y dado que todos los métodos son virtuales en Java, en realidad no es diferente.
Agent_L

0

Es simplemente fácil de hacer. Por ejemplo:

C subclase de B y B subclase de A. Ambos tienen el método methodName () por ejemplo.

public abstract class A {

    public void methodName() {
        System.out.println("Class A");
    }

}

public class B extends A {

    public void methodName() {
        super.methodName();
        System.out.println("Class B");
    }

    // Will call the super methodName
    public void hackSuper() {
        super.methodName();
    }

}

public class C extends B {

    public static void main(String[] args) {
        A a = new C();
        a.methodName();
    }

    @Override
    public void methodName() {
        /*super.methodName();*/
        hackSuper();
        System.out.println("Class C");
    }

}

Ejecutar clase C La salida será: Clase A Clase C

En lugar de salida: Clase A Clase B Clase C


-1
public class SubSubClass extends SubClass {

    @Override
    public void print() {
        super.superPrint();
    }

    public static void main(String[] args) {
        new SubSubClass().print();
    }
}

class SuperClass {

    public void print() {
        System.out.println("Printed in the GrandDad");
    }
}

class SubClass extends SuperClass {

    public void superPrint() {
        super.print();
    }
}

Salida: Impreso en el GrandDad


2
Esta respuesta está fuera del alcance de la pregunta. El OP no preguntó cómo llamar a un método en una clase de abuelos. El problema es una discusión de por qué el super.super.method()código no es válido en Java.
Jed Schaaf

-1

La palabra clave super es solo una forma de invocar el método en la superclase. En el tutorial de Java: https://docs.oracle.com/javase/tutorial/java/IandI/super.html

Si su método anula uno de los métodos de su superclase, puede invocar el método anulado mediante el uso de la palabra clave super.

¡No creas que es una referencia del súper objeto! No, es solo una palabra clave para invocar métodos en la superclase.

Aquí hay un ejemplo:

class Animal {
    public void doSth() {
        System.out.println(this);   // It's a Cat! Not an animal!
        System.out.println("Animal do sth.");
    }
}

class Cat extends Animal {
    public void doSth() {
        System.out.println(this);
        System.out.println("Cat do sth.");
        super.doSth();
    }
}

Cuando llame cat.doSth(), se imprimirá el método doSth()en clase y es un gato.Animalthis

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.