Java: Aufruf einer Supermethode, die eine überschriebene Methode aufruft

94
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();
    }
}

meine erwartete Ausgabe:

Unterklasse Methode1
Oberklasse Methode1
Oberklasse Methode2

tatsächliche Ausgabe:

Unterklassenmethode1
Oberklassenmethode1
Unterklassenmethode2

Ich weiß, dass ich technisch gesehen eine öffentliche Methode überschrieben habe, aber ich dachte mir, dass, weil ich den Super anrief, alle Anrufe innerhalb des Super im Super bleiben würden, dies nicht geschieht. Irgendwelche Ideen, wie ich es schaffen kann?

jsonfry
quelle
2
Ich vermute, Sie möchten vielleicht "die Komposition der Vererbung vorziehen".
Tom Hawtin - Tackline

Antworten:

79

Das Schlüsselwort super"klebt" nicht. Jeder Methodenaufruf wird einzeln behandelt. Selbst wenn Sie SuperClass.method1()ihn aufrufen supermüssen, hat dies keinen Einfluss auf andere Methodenaufrufe, die Sie möglicherweise in Zukunft ausführen werden.

Das heißt , es gibt keinen direkten Weg rufen SuperClass.method2()aus , SuperClass.method1()ohne allerdings zu gehen , SubClass.method2()wenn Sie mit einer tatsächlichen Instanz arbeiten SuperClass.

Mit Reflection können Sie nicht einmal den gewünschten Effekt erzielen (siehe Dokumentation vonjava.lang.reflect.Method.invoke(Object, Object...) ).

[EDIT] Es scheint immer noch einige Verwirrung zu geben. Lassen Sie mich eine andere Erklärung versuchen.

Wenn Sie aufrufen foo(), rufen Sie tatsächlich auf this.foo(). Mit Java können Sie einfach das weglassen this. Im Beispiel in der Frage, die Art der thisist SubClass.

Wenn Java den Code ausführt SuperClass.method1(), kommt er schließlich anthis.method2();

Die Verwendung superändert nicht die Instanz, auf die von verwiesen wird this. Der Anruf geht also an, SubClass.method2()da er thisvom Typ ist SubClass.

Vielleicht ist es einfacher zu verstehen, wenn Sie sich vorstellen, dass Java thisals versteckter erster Parameter übergeben wird:

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);
    }
}

Wenn Sie dem Aufrufstapel folgen, können Sie sehen, dass sich thisnichts ändert. Es ist immer die Instanz, in der erstellt wurde main().

Aaron Digulla
quelle
Könnte jemand bitte ein Diagramm hochladen (Wortspiel beabsichtigt), das durch den Stapel geht? Danke im Voraus!
Laienkatze
2
@laycat: Es ist kein Diagramm erforderlich. Denken Sie daran, dass Java kein "Gedächtnis" für hat super. Jedes Mal, wenn eine Methode aufgerufen wird, wird der Instanztyp überprüft und mit diesem Typ nach der Methode gesucht, unabhängig davon, wie oft Sie aufgerufen haben super. Wenn Sie also method2eine Instanz von aufrufen SubClass, wird immer die Instanz von SubClasszuerst angezeigt.
Aaron Digulla
@ AaronDigulla, kannst du mehr über das "Java hat keinen Speicher für Super" erklären?
MengT
@ Truman'sworld: wie ich in meiner Antwort sagte: using superändert nichts an der Instanz. Es wird kein verstecktes Feld gesetzt, "von nun an sollten alle Methodenaufrufe verwendet werden SuperClass". Oder thisanders ausgedrückt: Der Wert von ändert sich nicht.
Aaron Digulla
@AaronDigulla, heißt das also, dass das Super-Schlüsselwort tatsächlich die geerbten Methoden in der Unterklasse aufruft, anstatt zur Super-Klasse zu wechseln?
MengT
15

Sie können nur in den überschreibenden Methoden (oder in anderen Methoden der überschreibenden Klasse) auf überschriebene Methoden zugreifen.

Also: entweder nicht überschreiben method2()oder super.method2()innerhalb der überschriebenen Version aufrufen .

Sean Patrick Floyd
quelle
8

Sie verwenden das thisSchlüsselwort, das sich tatsächlich auf die "aktuell ausgeführte Instanz des von Ihnen verwendeten Objekts" bezieht, this.method2();dh Sie rufen in Ihrer Oberklasse auf, dh es ruft die Methode2 () für das Objekt auf, das Sie verwenden. wieder verwenden, das ist die Unterklasse.

Jose Diaz
quelle
8
true, und nicht zu verwenden thiswird auch nicht helfen. Ein unqualifizierter Aufruf verwendet implizitthis
Sean Patrick Floyd
3
Warum wird das positiv bewertet? Dies ist nicht die Antwort auf diese Frage. Wenn Sie schreiben, wird method2()der Compiler sehen this.method2(). Selbst wenn Sie das entfernen, thisfunktioniert es immer noch nicht. Was @ Sean Patrick Floyd sagt, ist richtig
Shervin Asgari
4
@Shervin er sagt nichts falsches, er macht nur nicht klar, was passiert, wenn Sie this
Sean Patrick Floyd
4
Die Antwort ist richtig, wenn darauf hingewiesen wird, dass sie thissich auf die "konkrete laufende Instanzklasse" (in der Laufzeit bekannt) und nicht (wie das Poster zu glauben scheint) auf die "aktuelle Compilation-Unit-Klasse" (in der das Schlüsselwort verwendet wird, bekannt in) bezieht Kompilierungszeit). Es kann aber auch irreführend sein (wie Shervin betont): thiswird auch implizit mit dem einfachen Methodenaufruf referenziert; method2();ist das gleiche wiethis.method2();
leonbloy
7

Ich denke so

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

Lassen Sie mich diese Unterklasse ein wenig nach links verschieben, um zu zeigen, was sich darunter befindet ... (Mann, ich liebe ASCII-Grafiken)

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).

Mit anderen Worten, zitiert aus der Java-Sprachspezifikation :

Das Formular super.Identifierbezieht sich auf das Feld mit dem Namen Identifierdes aktuellen Objekts, wobei das aktuelle Objekt jedoch als Instanz der Oberklasse der aktuellen Klasse angesehen wird.

Das Formular T.super.Identifierbezieht sich auf das Feld Identifierder lexikalisch einschließenden Instanz, die dieser Instanz entspricht T, jedoch als Instanz der Oberklasse von angesehen wird T.

Für Laien thisist es im Grunde ein Objekt (* das ** Objekt; dasselbe Objekt, das Sie in Variablen verschieben können), die Instanz der instanziierten Klasse, eine einfache Variable in der Datendomäne; superist wie ein Zeiger auf einen geliehenen Codeblock, den Sie ausführen möchten, eher wie ein bloßer Funktionsaufruf, und er ist relativ zu der Klasse, in der er aufgerufen wird.

Wenn Sie also superaus der Oberklasse verwenden, erhalten Sie Code von der Superduper-Klasse [dem Großelternteil], der ausgeführt wird. Wenn Sie thiseine Oberklasse verwenden (oder implizit verwenden), zeigt sie weiterhin auf die Unterklasse (weil niemand sie geändert hat - und niemand könnten).

Unai Vivi
quelle
2

Wenn Sie nicht möchten, dass superClass.method1 subClass.method2 aufruft, machen Sie method2 privat, damit es nicht überschrieben werden kann.

Hier ist ein Vorschlag:

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");
  }
}

Wenn es nicht so funktionieren würde, wäre Polymorphismus unmöglich (oder zumindest nicht halb so nützlich).

Joeri Hendrickx
quelle
2

Da die einzige Möglichkeit , ein Verfahren zu vermeiden , überschriebenen zu bekommen , ist das Schlüsselwort verwenden Super , habe ich gedacht , um die method2 () aus nach oben Oberklasse zu einer anderen neuen Basisklasse und es dann aus rufen Oberklasse :

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();
    }
}

Ausgabe:

subclass method1
superclass method1
superclass method2
carlos_lm
quelle
2

this bezieht sich immer auf das aktuell ausgeführte Objekt.

Um den Punkt hier weiter zu veranschaulichen, ist eine einfache Skizze:

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

Wenn Sie eine Instanz der äußeren Box haben, ein SubclassObjekt, wo immer Sie sich in die Box wagen, sogar in den Superclass'Bereich', ist es immer noch die Instanz der äußeren Box.

Darüber hinaus gibt es in diesem Programm nur ein Objekt, das aus den drei Klassen erstellt wird. Es thiskann sich also immer nur auf eine Sache beziehen, und zwar:

Geben Sie hier die Bildbeschreibung ein

wie im Netbeans 'Heap Walker' gezeigt.

Johnny Baloney
quelle
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");
    }
}

Berufung

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

Ausgänge

Unterklasse Methode1
Oberklasse Methode1
Oberklasse Methode2

Vijay
quelle
1

Ich glaube nicht, dass Sie es direkt tun können. Eine Problemumgehung wäre, eine private interne Implementierung von Methode2 in der Oberklasse zu haben und diese aufzurufen. Beispielsweise:

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");
    }

}
David Gelhar
quelle
1

Das Schlüsselwort "this" bezieht sich auf die aktuelle Klassenreferenz. Das heißt, wenn es innerhalb der Methode verwendet wird, ist die 'aktuelle' Klasse immer noch Unterklasse, und daher wird die Antwort erklärt.

Ozil
quelle
1

Zusammenfassend zeigt dies auf das aktuelle Objekt und der Methodenaufruf in Java ist von Natur aus polymorph. Die Auswahl der Methode für die Ausführung hängt also vollständig von dem Objekt ab, auf das dies zeigt. Daher ruft das Aufrufen der Methode method2 () von der übergeordneten Klasse die Methode2 () der untergeordneten Klasse auf, da dies auf das Objekt der untergeordneten Klasse verweist. Die Definition ändert sich nicht, unabhängig von der verwendeten Klasse.

PS. Im Gegensatz zu Methoden sind Mitgliedsvariablen der Klasse nicht polymorph.

Mangu Singh Rajpurohit
quelle
0

Während meiner Recherche nach einem ähnlichen Fall habe ich die Stapelverfolgung in der Unterklassenmethode überprüft, um herauszufinden, woher der Aufruf kommt. Es gibt wahrscheinlich intelligentere Möglichkeiten, dies zu tun, aber es funktioniert für mich und ist ein dynamischer Ansatz.

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
        }
}

Ich denke, die Frage nach einer Lösung für den Fall ist vernünftig. Es gibt natürlich Möglichkeiten, das Problem mit verschiedenen Methodennamen oder sogar verschiedenen Parametertypen zu lösen, wie bereits im Thread erwähnt, aber in meinem Fall möchte ich nicht durch verschiedene Methodennamen verwechseln.

Schlage Siegrist
quelle
Dieser Code ist gefährlich und riskant und teuer. Das Erstellen von Ausnahmen erfordert, dass die VM eine vollständige Stapelverfolgung erstellt. Der Vergleich nur des Namens und nicht der vollständigen Signatur ist fehleranfällig. Außerdem stinkt es nach einem großen Designfehler.
M. le Rutte
Unter dem Gesichtspunkt der Leistung scheint mein Code keine größere Wirkung zu erzielen als 'new HashMap (). Size ()'. Möglicherweise habe ich jedoch die Bedenken, über die Sie nachgedacht haben, übersehen, und ich bin kein VM-Experte. Ich sehe Ihren Zweifel, wenn ich die Klassennamen vergleiche, aber es enthält das Paket, was mich ziemlich sicher macht, dass es meine Elternklasse ist. Wie auch immer, ich mag die Idee, stattdessen die Signatur zu vergleichen. Wie würden Sie das tun? Im Allgemeinen würde ich mich freuen, wenn Sie auf eine reibungslosere Weise feststellen können, ob der Anrufer die Oberklasse oder jemand anderes ist.
Beat Siegrist
Wenn Sie feststellen müssen, ob der Anrufer eine Superklasse ist, würde ich ernsthaft länger darüber nachdenken, ob ein Redesign vorhanden ist. Dies ist ein Anti-Muster.
M. le Rutte
Ich sehe den Punkt, aber die allgemeine Anfrage des Threads ist vernünftig. In einigen Situationen kann es sinnvoll sein, dass ein Methodenaufruf der Oberklasse bei jedem verschachtelten Methodenaufruf im Kontext der Oberklasse bleibt. Es scheint jedoch keine Möglichkeit zu geben, den Methodenaufruf in der Superklasse entsprechend zu steuern.
Beat Siegrist
0

Wenn die Ausgabe der aufgeworfenen Frage weiter erweitert wird, erhalten Sie mehr Einblick in den Zugriffsspezifizierer und das Überschreibungsverhalten.

            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
Sudhirkondle
quelle