Java: llamar a un supermétodo que llama a un método anulado


97
public class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        this.method2();
    }

    public void method2()
    {
        System.out.println("superclass method2");
    }

}

public class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}



public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1();
    }
}

mi salida esperada:

subclase método1
superclase método1
superclase método2

salida real:

subclase método1
superclase método1
subclase método2

Sé que técnicamente he anulado un método público, pero pensé que debido a que estaba llamando al super, cualquier llamada dentro del super permanecería en el super, esto no está sucediendo. ¿Alguna idea de cómo puedo hacer que suceda?


2
Sospecho que es posible que desee "preferir la composición a la herencia".
Tom Hawtin - tackline

Respuestas:


80

La palabra clave superno "se pega". Cada llamada a método se maneja individualmente, por lo que incluso si lo hiciste SuperClass.method1()llamando super, eso no influye en ninguna otra llamada a método que puedas realizar en el futuro.

Eso significa que no hay una forma directa para llamar SuperClass.method2()desde SuperClass.method1()sin ir aunque SubClass.method2()menos que usted está trabajando con una instancia real de SuperClass.

Ni siquiera puede lograr el efecto deseado usando Reflection (consulte la documentación dejava.lang.reflect.Method.invoke(Object, Object...) ).

[EDITAR] Todavía parece haber cierta confusión. Déjame intentar una explicación diferente.

Cuando invocas foo(), en realidad invocas this.foo(). Java simplemente le permite omitir elthis . En el ejemplo de la pregunta, el tipo de thises SubClass.

Entonces, cuando Java ejecuta el código en SuperClass.method1() , finalmente llega athis.method2();

El uso superno cambia la instancia a la que apunta this. Entonces la llamada va aSubClass.method2() ya que thises de tipo SubClass.

Tal vez sea más fácil de entender cuando imagina que Java pasa this como un primer parámetro oculto:

public class SuperClass
{
    public void method1(SuperClass this)
    {
        System.out.println("superclass method1");
        this.method2(this); // <--- this == mSubClass
    }

    public void method2(SuperClass this)
    {
        System.out.println("superclass method2");
    }

}

public class SubClass extends SuperClass
{
    @Override
    public void method1(SubClass this)
    {
        System.out.println("subclass method1");
        super.method1(this);
    }

    @Override
    public void method2(SubClass this)
    {
        System.out.println("subclass method2");
    }
}



public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1(mSubClass);
    }
}

Si sigue la pila de llamadas, puede ver que thisnunca cambia, siempre es la instancia creada en main().


¿Podría alguien cargar un diagrama de esto (juego de palabras) pasando por la pila? ¡gracias por adelantado!
laycat

2
@laycat: No es necesario un diagrama. Solo recuerda que Java no tiene "memoria" para super. Cada vez que llama a un método, mirará el tipo de instancia y comenzará a buscar el método con este tipo, sin importar la frecuencia con la que lo llame super. Por lo tanto, cuando llame method2a una instancia de SubClass, siempre verá la SubClassprimera.
Aaron Digulla

@AaronDigulla, ¿Puedes explicar más sobre "Java no tiene memoria para super"?
MengT

@ Truman'sworld: como dije en mi respuesta: usar superno cambia la instancia. No establece ningún campo oculto "a partir de ahora, todas las llamadas a métodos deberían comenzar a usarse SuperClass". O para decirlo de otra manera: el valor de thisno cambia.
Aaron Digulla

@AaronDigulla, ¿eso significa que la súper palabra clave en realidad invoca los métodos heredados en la subclase en lugar de ir a la súper clase?
MengT

15

Solo puede acceder a los métodos reemplazados en los métodos de reemplazo (o en otros métodos de la clase de reemplazo).

Entonces: o no anule method2()o llame super.method2()dentro de la versión anulada.


8

Estás usando la thispalabra clave que en realidad se refiere a la "instancia en ejecución del objeto que estás usando", es decir, estás invocando this.method2();en tu superclase, es decir, llamará al método2 () en el objeto que ' que está usando, que es la subclase.


8
cierto, y no usarlo thistampoco ayudará. Una invocación no calificada utiliza implícitamentethis
Sean Patrick Floyd

3
¿Por qué se vota a favor de esto? Ésta no es la respuesta a esta pregunta. Cuando escriba method2(), verá el compilador this.method2(). Entonces, incluso si quita el this, aún no funcionará. Lo que @Sean Patrick Floyd está diciendo es correcto
Shervin Asgari

4
@Shervin no está diciendo nada malo, simplemente no está dejando en claro lo que sucede si lo dejas fuerathis
Sean Patrick Floyd

4
La respuesta es correcta al señalar que se thisrefiere a la "clase de instancia de ejecución concreta" (conocida en tiempo de ejecución) y no (como parece creer el autor) a la "clase de unidad de compilación actual" (donde se usa la palabra clave, conocida en tiempo de compilación). Pero también puede ser engañoso (como señala Shervin): thistambién se hace referencia implícitamente con la llamada al método simple; method2();es lo mismo quethis.method2();
leonbloy

7

Lo pienso de esta manera

+----------------+
|     super      |
+----------------+ <-----------------+
| +------------+ |                   |
| |    this    | | <-+               |
| +------------+ |   |               |
| | @method1() | |   |               |
| | @method2() | |   |               |
| +------------+ |   |               |
|    method4()   |   |               |
|    method5()   |   |               |
+----------------+   |               |
    We instantiate that class, not that one!

Déjame mover esa subclase un poco hacia la izquierda para revelar lo que hay debajo ... (Hombre, me encantan los gráficos ASCII)

We are here
        |
       /  +----------------+
      |   |     super      |
      v   +----------------+
+------------+             |
|    this    |             |
+------------+             |
| @method1() | method1()   |
| @method2() | method2()   |
+------------+ method3()   |
          |    method4()   |
          |    method5()   |
          +----------------+

Then we call the method
over here...
      |               +----------------+
 _____/               |     super      |
/                     +----------------+
|   +------------+    |    bar()       |
|   |    this    |    |    foo()       |
|   +------------+    |    method0()   |
+-> | @method1() |--->|    method1()   | <------------------------------+
    | @method2() | ^  |    method2()   |                                |
    +------------+ |  |    method3()   |                                |
                   |  |    method4()   |                                |
                   |  |    method5()   |                                |
                   |  +----------------+                                |
                   \______________________________________              |
                                                          \             |
                                                          |             |
...which calls super, thus calling the super's method1() here, so that that
method (the overidden one) is executed instead[of the overriding one].

Keep in mind that, in the inheritance hierarchy, since the instantiated
class is the sub one, for methods called via super.something() everything
is the same except for one thing (two, actually): "this" means "the only
this we have" (a pointer to the class we have instantiated, the
subclass), even when java syntax allows us to omit "this" (most of the
time); "super", though, is polymorphism-aware and always refers to the
superclass of the class (instantiated or not) that we're actually
executing code from ("this" is about objects [and can't be used in a
static context], super is about classes).

En otras palabras, citando la especificación del lenguaje Java :

El formulario se super.Identifierrefiere al campo denominadoIdentifier del objeto actual, pero con el objeto actual visto como una instancia de la superclase de la clase actual.

La forma se T.super.Identifierrefiere al campo nombrado Identifierde la instancia que encierra léxicamente correspondiente a T, pero con esa instancia vista como una instancia de la superclase de T.

En términos thissencillos , es básicamente un objeto (* el ** objeto; el mismo objeto que se puede mover en variables), la instancia de la clase instanciada, una variable simple en el dominio de datos; superes como un puntero a un bloque de código prestado que desea que se ejecute, más como una mera llamada a función, y es relativo a la clase donde se llama.

Por lo tanto, si usa superde la superclase, obtiene código de la clase superduper [el abuelo] ejecutado), mientras que si usa this(o si se usa implícitamente) de una superclase, sigue apuntando a la subclase (porque nadie lo ha cambiado, y nadie podría).


2

Si no desea que superClass.method1 llame a subClass.method2, haga que el método2 sea privado para que no se pueda anular.

He aquí una sugerencia:

public class SuperClass {

  public void method1() {
    System.out.println("superclass method1");
    this.internalMethod2();
  }

  public void method2()  {
    // this method can be overridden.  
    // It can still be invoked by a childclass using super
    internalMethod2();
  }

  private void internalMethod2()  {
    // this one cannot.  Call this one if you want to be sure to use
    // this implementation.
    System.out.println("superclass method2");
  }

}

public class SubClass extends SuperClass {

  @Override
  public void method1() {
    System.out.println("subclass method1");
    super.method1();
  }

  @Override
  public void method2() {
    System.out.println("subclass method2");
  }
}

Si no funcionara de esta manera, el polimorfismo sería imposible (o al menos ni la mitad de útil).


2

Dado que la única forma de evitar que un método se anule es usar la palabra clave super , he pensado en subir el método2 () de SuperClass a otra nueva clase Base y luego llamarlo desde SuperClass :

class Base 
{
    public void method2()
    {
        System.out.println("superclass method2");
    }
}

class SuperClass extends Base
{
    public void method1()
    {
        System.out.println("superclass method1");
        super.method2();
    }
}

class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}

public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1();
    }
}

Salida:

subclass method1
superclass method1
superclass method2

2

this siempre se refiere al objeto que se está ejecutando actualmente.

Para ilustrar más el punto, aquí hay un bosquejo simple:

+----------------+
|  Subclass      |
|----------------|
|  @method1()    |
|  @method2()    |
|                |
| +------------+ |
| | Superclass | |
| |------------| |
| | method1()  | |
| | method2()  | |
| +------------+ |
+----------------+

Si tiene una instancia de la caja exterior, un Subclassobjeto, dondequiera que se aventure dentro de la caja, incluso en el Superclass'área', sigue siendo la instancia de la caja exterior.

Además, en este programa solo hay un objeto que se crea de las tres clases, por thislo que solo puede hacer referencia a una cosa y es:

ingrese la descripción de la imagen aquí

como se muestra en "Heap Walker" de Netbeans .


2
class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        SuperClass se=new SuperClass();
        se.method2();
    }

    public void method2()
    {
        System.out.println("superclass method2");
    }
}


class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}

vocación

SubClass mSubClass = new SubClass();
mSubClass.method1();

salidas

subclase método1
superclase método1
superclase método2


1

No creo que puedas hacerlo directamente. Una solución alternativa sería tener una implementación interna privada del método2 en la superclase y llamar a eso. Por ejemplo:

public class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        this.internalMethod2();
    }

    public void method2()
    {
        this.internalMethod2(); 
    }
    private void internalMethod2()
    {
        System.out.println("superclass method2");
    }

}

1

"esta" palabra clave se refiere a la referencia de clase actual. Eso significa que, cuando se usa dentro del método, la clase 'actual' sigue siendo SubClass y, por lo tanto, se explica la respuesta.


1

Para resumir, esto apunta al objeto actual y la invocación del método en Java es polimórfica por naturaleza. Entonces, la selección del método para la ejecución depende totalmente del objeto señalado por este. Por lo tanto, la invocación del método method2 () de la clase principal invoca el método2 () de la clase secundaria, ya que this apunta al objeto de la clase secundaria. La definición de esto no cambia, independientemente de la clase que se use.

PD. a diferencia de los métodos, las variables miembro de clase no son polimórficas.


0

Durante mi investigación para un caso similar, terminé verificando el seguimiento de la pila en el método de subclase para averiguar de dónde proviene la llamada. Probablemente haya formas más inteligentes de hacerlo, pero me funciona y es un enfoque dinámico.

public void method2(){
        Exception ex=new Exception();
        StackTraceElement[] ste=ex.getStackTrace();
        if(ste[1].getClassName().equals(this.getClass().getSuperclass().getName())){
            super.method2();
        }
        else{
            //subclass method2 code
        }
}

Creo que la cuestión de tener una solución para el caso es razonable. Por supuesto, hay formas de resolver el problema con diferentes nombres de métodos o incluso con diferentes tipos de parámetros, como ya se mencionó en el hilo, pero en mi caso no me gustaría confundir con diferentes nombres de métodos.


Este código es peligroso, arriesgado y costoso. La creación de excepciones requiere que la máquina virtual construya un seguimiento de pila completo, comparar solo el nombre y no la firma completa es propenso a errores. Además, huele a un gran defecto de diseño.
M. le Rutte

Desde el punto de vista del rendimiento, mi código no parece producir más impacto que 'new HashMap (). Size ()'. Sin embargo, es posible que haya pasado por alto las preocupaciones en las que ha estado pensando y, por lo general, no soy un experto en VM. Veo su duda al comparar los nombres de las clases, pero incluye el paquete, lo que me hace estar bastante seguro, es mi clase principal. De todos modos, me gusta la idea de comparar la firma en su lugar, ¿cómo harías eso? En general, si tiene una forma más sencilla de determinar si la persona que llama es la superclase o cualquier otra persona, le agradecería que hablara.
Vence a Siegrist el

Si necesita determinar si la persona que llama es una superclase, lo pensaría seriamente por más tiempo si se implementa un rediseño. Este es un anti-patrón.
M. le Rutte

Veo el punto, pero la solicitud general del hilo es razonable. En alguna situación, puede tener sentido que una llamada de método de superclase permanezca en el contexto de superclase con cualquier llamada de método anidado. Sin embargo, parece que no hay forma de dirigir la llamada al método en consecuencia en la Superclase.
Vence a Siegrist el

0

Más extendido el resultado de la pregunta planteada, esto dará más información sobre el especificador de acceso y el comportamiento de anulación.

            package overridefunction;
            public class SuperClass 
                {
                public void method1()
                {
                    System.out.println("superclass method1");
                    this.method2();
                    this.method3();
                    this.method4();
                    this.method5();
                }
                public void method2()
                {
                    System.out.println("superclass method2");
                }
                private void method3()
                {
                    System.out.println("superclass method3");
                }
                protected void method4()
                {
                    System.out.println("superclass method4");
                }
                void method5()
                {
                    System.out.println("superclass method5");
                }
            }

            package overridefunction;
            public class SubClass extends SuperClass
            {
                @Override
                public void method1()
                {
                    System.out.println("subclass method1");
                    super.method1();
                }
                @Override
                public void method2()
                {
                    System.out.println("subclass method2");
                }
                // @Override
                private void method3()
                {
                    System.out.println("subclass method3");
                }
                @Override
                protected void method4()
                {
                    System.out.println("subclass method4");
                }
                @Override
                void method5()
                {
                    System.out.println("subclass method5");
                }
            }

            package overridefunction;
            public class Demo 
            {
                public static void main(String[] args) 
                {
                    SubClass mSubClass = new SubClass();
                    mSubClass.method1();
                }
            }

            subclass method1
            superclass method1
            subclass method2
            superclass method3
            subclass method4
            subclass method5
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.