Java: wywołanie super metody, która wywołuje metodę przesłoniętą

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

mój oczekiwany wynik:

podklasa method1
nadklasą method1
nadklasą Method2

rzeczywista wydajność:

podklasa method1
nadklasą method1
podklasa Method2

Wiem, technicznie rzecz biorąc, zastąpiłem publiczną metodę, ale pomyślałem, że skoro dzwonię do super, wszelkie połączenia z super pozostaną w super, to się nie dzieje. Jakieś pomysły, jak mogę to zrobić?

jsonfry
źródło
2
Podejrzewam, że możesz chcieć „przedkładać kompozycję nad dziedziczenie”.
Tom Hawtin - tackline

Odpowiedzi:

81

Słowo kluczowe supernie „przykleja się”. Każde wywołanie metody jest traktowane indywidualnie, więc nawet jeśli masz do SuperClass.method1()dzwoniąc super, że nie ma wpływu na wszelkie inne wywołanie metody, które można zrobić w przyszłości.

Oznacza to, że nie istnieje bezpośredni sposób, aby zadzwonić SuperClass.method2()z SuperClass.method1()bez przechodzenia choć SubClass.method2()chyba że pracujesz z rzeczywistej instancji SuperClass.

Nie możesz nawet osiągnąć pożądanego efektu za pomocą Reflection (patrz dokumentacjajava.lang.reflect.Method.invoke(Object, Object...) ).

[EDYCJA] Wciąż wydaje się, że jest pewne zamieszanie. Spróbuję innego wyjaśnienia.

Kiedy wzywasz foo(), faktycznie wzywasz this.foo(). Java po prostu pozwala pominąć rozszerzenie this. W przykładzie w pytaniu typ thisto SubClass.

Więc kiedy Java wykonuje kod, w SuperClass.method1()końcu dociera dothis.method2();

Użycie supernie zmienia instancji wskazywanej przez this. Więc połączenie jest kierowane do, SubClass.method2()ponieważ thisjest typu SubClass.

Może łatwiej jest to zrozumieć, wyobrażając sobie, że Java jest przekazywana thisjako ukryty pierwszy parametr:

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

Jeśli podążasz za stosem wywołań, zobaczysz, że thisnigdy się nie zmienia, zawsze jest to instancja utworzona w main().

Aaron Digulla
źródło
czy ktoś mógłby przesłać schemat tego (zamierzonego kalambura) przechodzenia przez stos? z góry dziękuję!
laycat
2
@laycat: Nie ma potrzeby tworzenia diagramu. Pamiętaj tylko, że Java nie ma „pamięci” super. Za każdym razem, gdy wywołuje metodę, sprawdzi typ instancji i rozpocznie wyszukiwanie metody z tym typem, bez względu na to, jak często ją wywoływałeś super. Więc kiedy wywołasz method2instancję SubClass, zawsze zobaczy ona tę od SubClasspoczątku.
Aaron Digulla,
@AaronDigulla, Czy możesz wyjaśnić więcej na temat „Java nie ma pamięci na super”?
MengT
@ Truman'sworld: jak powiedziałem w mojej odpowiedzi: użycie supernie zmienia instancji. Nie ustawia jakiegoś ukrytego pola „od teraz wszystkie wywołania metod powinny zacząć używać SuperClass”. Albo inaczej: wartość thissię nie zmienia.
Aaron Digulla
@AaronDigulla, czy to oznacza, że ​​słowo kluczowe super faktycznie wywołuje odziedziczone metody z podklasy zamiast przechodzić do superklasy?
MengT
15

Dostęp do zastąpionych metod można uzyskać tylko w metodach przesłaniających (lub w innych metodach klasy przesłaniającej).

Tak więc: albo nie nadpisuj, method2()ani nie wywołuj super.method2()w nadpisanej wersji.

Sean Patrick Floyd
źródło
8

Używasz thissłowa kluczowego, które faktycznie odnosi się do "aktualnie działającej instancji obiektu, którego używasz", to this.method2();znaczy, że wywołujesz swoją nadklasę, to znaczy wywoła ona metodę method2 () na obiekcie, który ' ponownie używam, czyli podklasę.

Jose Diaz
źródło
9
prawda i nieużywanie też thisnie pomoże. Niekwalifikowane wezwanie pośrednio używathis
Sean Patrick Floyd
3
Dlaczego to się cieszy? To nie jest odpowiedź na to pytanie. Kiedy napiszesz, method2()kompilator zobaczy this.method2(). Więc nawet jeśli go usuniesz this, nadal nie będzie działać. To, co mówi @Sean Patrick Floyd, jest poprawne
Shervin Asgari,
4
@Shervin nie mówi nic złego, po prostu nie wyjaśnia, co się stanie, jeśli opuściszthis
Sean Patrick Floyd
4
Odpowiedź jest słuszna, wskazując, że thisodnosi się do „konkretnej klasy uruchomionej instancji” (znanej w środowisku wykonawczym), a nie (jak zdaje się sądzić plakat) do „bieżącej klasy jednostki kompilacji” (gdzie używane jest słowo kluczowe, znane w czas kompilacji). Ale może też wprowadzać w błąd (jak wskazuje Shervin): thisodwołuje się również niejawnie za pomocą zwykłego wywołania metody; method2();jest taki sam jakthis.method2();
leonbloy
7

Myślę o tym w ten sposób

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

Pozwólcie, że przesunę tę podklasę trochę w lewo, aby pokazać, co jest pod spodem ... (Człowieku, uwielbiam grafikę 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).

Innymi słowy, cytując ze specyfikacji języka Java :

Formularz super.Identifierodnosi się do pola o nazwie Identifierbieżącego obiektu, ale z bieżącym obiektem wyświetlanym jako instancja nadklasy bieżącej klasy.

Formularz T.super.Identifierodnosi się do pola nazwanego Identifierinstancją obejmującą leksyk, odpowiadającą instancji T, ale z tą instancją postrzeganą jako instancja nadklasy klasy T.

Mówiąc prościej, thisjest w zasadzie obiektem (* obiekt **; ten sam obiekt, po którym można się poruszać w zmiennych), instancją klasy, której dotyczy instancja, zwykłą zmienną w domenie danych; superjest jak wskaźnik do pożyczonego bloku kodu, który chcesz wykonać, bardziej przypomina zwykłe wywołanie funkcji i jest względny w stosunku do klasy, w której jest wywoływany.

Dlatego jeśli używasz superz nadklasy, otrzymujesz kod z wykonanej klasy superduper [dziadek], podczas gdy jeśli używasz this(lub jeśli jest używana niejawnie) z nadklasy, wskazuje ona na podklasę (ponieważ nikt jej nie zmienił - i nikt mógłby).

Unai Vivi
źródło
3
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");
    }
}

powołanie

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

wyjścia

podklasa method1
nadklasą method1
nadklasą Method2

Vijay
źródło
2

Jeśli nie chcesz, aby superClass.method1 wywoływał subClass.method2, ustaw metodę method2 jako prywatną, aby nie można jej było zastąpić.

Oto sugestia:

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

Gdyby to nie zadziałało w ten sposób, polimorfizm byłby niemożliwy (a przynajmniej nie w połowie tak użyteczny).

Joeri Hendrickx
źródło
2

Ponieważ jedynym sposobem uniknięcia zastąpienia metody jest użycie słowa kluczowego super , pomyślałem, aby przenieść metodę method2 () z SuperClass w górę do innej nowej klasy bazowej, a następnie wywołać ją z 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();
    }
}

Wynik:

subclass method1
superclass method1
superclass method2
carlos_lm
źródło
2

this zawsze odnosi się do aktualnie wykonywanego obiektu.

Aby lepiej zilustrować ten punkt, oto prosty szkic:

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

Jeśli masz wystąpienie zewnętrznego pudełka, Subclassobiekt, gdziekolwiek zdarzy ci się zapuścić do środka pudełka, nawet do Superclass„obszaru”, nadal jest to wystąpienie zewnętrznego pudełka.

Co więcej, w tym programie jest tylko jeden obiekt, który jest tworzony z trzech klas, więc zawsze thismoże odnosić się tylko do jednej rzeczy, a jest to:

wprowadź opis obrazu tutaj

jak pokazano w Netbeans 'Heap Walker'.

Johnny Baloney
źródło
1

Nie wierzę, że możesz to zrobić bezpośrednio. Jednym obejściem byłoby posiadanie prywatnej wewnętrznej implementacji method2 w superklasie i wywołanie tego. Na przykład:

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
źródło
1

Słowo kluczowe „this” odnosi się do aktualnej klasy. Oznacza to, że gdy jest używana wewnątrz metody, „bieżąca” klasa nadal jest podklasą, więc odpowiedź jest wyjaśniana.

Ozil
źródło
1

Podsumowując, wskazuje to na bieżący obiekt, a wywołanie metody w Javie jest z natury polimorficzne. Zatem wybór metody do wykonania całkowicie zależy od wskazanego obiektu. Dlatego wywołanie metody method2 () z klasy nadrzędnej wywołuje metodę method2 () klasy potomnej, ponieważ this wskazuje na obiekt klasy potomnej. Definicja tego się nie zmienia, niezależnie od tego, której klasy jest używana.

PS. w przeciwieństwie do metod, zmienne składowe klasy nie są polimorficzne.

Mangu Singh Rajpurohit
źródło
0

Podczas moich badań dotyczących podobnego przypadku kończyłem sprawdzaniem śladu stosu w metodzie podklasy, aby dowiedzieć się, skąd pochodzi wywołanie. Prawdopodobnie istnieją mądrzejsze sposoby, aby to zrobić, ale to działa i jest to dynamiczne podejście.

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

Myślę, że pytanie o rozwiązanie tej sprawy jest rozsądne. Istnieją oczywiście sposoby rozwiązania problemu za pomocą różnych nazw metod lub nawet różnych typów parametrów, jak już wspomniano w wątku, ale w moim przypadku nie lubię mylić różnych nazw metod.

Pokonaj Siegrista
źródło
Ten kod jest niebezpieczny, ryzykowny i drogi. Tworzenie wyjątków wymaga, aby maszyna wirtualna utworzyła pełny ślad stosu, porównując tylko nazwę, a nie pełny podpis, jest podatne na błędy. Ponadto cuchnie ogromną wadą projektową.
M. le Rutte
Z punktu widzenia wydajności mój kod nie wydaje się wywierać większego wpływu niż „new HashMap (). Size ()”. Jednak mogłem przeoczyć obawy, o których myślałeś i nie jestem ekspertem od maszyn wirtualnych. Widzę twoje wątpliwości, porównując nazwy klas, ale zawiera pakiet, co daje mi całkowitą pewność, że to moja klasa nadrzędna. W każdym razie podoba mi się pomysł porównania podpisu, jak byś to zrobił? Ogólnie rzecz biorąc, jeśli masz płynniejszy sposób określenia, czy dzwoniący jest superklasą, czy kimkolwiek innym, o czym byłbym wdzięczny.
Pokonaj Siegrist
Jeśli chcesz ustalić, czy dzwoniący jest superklasą, poważnie bym się zastanowił, czy przeprojektowanie jest na miejscu. To jest anty-wzór.
M. le Rutte
Rozumiem, ale ogólne żądanie wątku jest rozsądne. W niektórych sytuacjach może mieć sens, że wywołanie metody nadklasy pozostaje w kontekście nadklasy z każdym wywołaniem metody zagnieżdżonej. Jednak wydaje się, że nie ma sposobu na odpowiednie skierowanie wywołania metody w nadklasie.
Pokonaj Siegrist
0

Jeszcze bardziej rozszerzone dane wyjściowe zadanego pytania, zapewni lepszy wgląd w specyfikator dostępu i zachowanie nadpisania.

            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
źródło