Dlaczego jest super.super.method (); niedozwolone w Javie?

360

Przeczytałem to pytanie i pomyślałem, że łatwo byłoby rozwiązać (nie bez tego, że nie da się go rozwiązać), gdyby można napisać:

@Override
public String toString() {
    return super.super.toString();
}

Nie jestem pewien, czy jest przydatny w wielu przypadkach, ale zastanawiam się, dlaczego tak nie jest i czy coś takiego istnieje w innych językach.

Co myślicie?

EDYCJA: Aby wyjaśnić: tak, wiem, w Javie jest to niemożliwe i tak naprawdę nie tęsknię. Nie spodziewałem się, że zadziała i byłem zaskoczony otrzymaniem błędu kompilatora. Właśnie wpadłem na pomysł i lubię o tym dyskutować.

Tim Büthe
źródło
7
Chęć połączenia jest super.super.toString()sprzeczna z twoją własną decyzją, kiedy zdecydujesz się rozszerzyć klasę, akceptując w ten sposób wszystkie (a nie niektóre) jej cechy.
DayaMoon,

Odpowiedzi:

484

Narusza to kapsułkowanie. Nie powinieneś być w stanie ominąć zachowania klasy nadrzędnej. Czasami warto ominąć zachowanie własnej klasy (szczególnie z tej samej metody), ale nie zachowanie rodzica. Załóżmy na przykład, że mamy podstawową „kolekcję przedmiotów”, podklasę reprezentującą „kolekcję czerwonych przedmiotów” i podklasę tej reprezentującą „kolekcję dużych czerwonych przedmiotów”. Sensowne jest posiadanie:

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

W porządku - RedItems zawsze może być pewien, że wszystkie zawarte w nim przedmioty są czerwone. Teraz załóżmy, że byli w stanie wywoł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);
    }
}

Teraz możemy dodać, co chcemy, a niezmiennik w RedItemsjest zepsuty.

Czy to ma sens?

Jon Skeet
źródło
38
niezły przykład. ale myślałem, że to zły projekt, gdy klasa podstawowa akceptuje przedmioty, ale klasa pochodna je odrzuca, ponieważ pochodna nie może być używana jako zamiennik dla klasy podstawowej (naruszenie zasady podstawienia). czy to właściwe myślenie, czy też taka hierarchia jest czysta?
Johannes Schaub - litb
5
Masz na myśli kompozycję zamiast dziedziczenia? Ten przykład nie jest powodem do unikania dziedziczenia - chociaż istnieje wiele innych.
Jon Skeet
3
@Tim: To był łatwy do zrozumienia przykład naruszenia enkapsulacji. Zamiast tego może to być ustawienie właściwości. Poza tym nie wszystkie aspekty będą widoczne na poziomie typu. Generics nie jest odpowiedzią na wszystko.
Jon Skeet
12
@ JohannesSchaub-litb Myślę, że narusza to zasadę podstawienia Liskowa, ponieważ jeśli jeden koduje kontrakt z Przedmiotem i używa instancji RedItems, otrzyma nieoczekiwany NotRedItemException. Zawsze uczono mnie, że podklasa powinna pobierać super zestaw danych wejściowych i zwracać podzbiór danych wyjściowych. Innymi słowy, podklasa nigdy nie powinna odrzucać danych wejściowych, które są poprawne dla superklasy lub generować danych wyjściowych, których superklasa nie jest w stanie wygenerować, co obejmuje zgłaszanie błędu, którego nie generuje superklasa.
Konstantin Tarashchanskiy
4
@piechuckerr: Czasami książki, czasem posty na blogach, czasem po prostu przeżyj ...
Jon Skeet
72

Myślę, że Jon Skeet ma poprawną odpowiedź. Chciałbym tylko dodać, że można uzyskać dostęp do zmiennych zaciemnionych z nadklas superklas, przesyłając 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();
        }
}

co daje wynik:

x = 3
super.x = 2
((T2) to). X = 2
((T1) to). X = 1
((I) this) .x = 0

(przykład z JLS )

Nie działa to jednak w przypadku wywołań metod, ponieważ wywołania metod są określane na podstawie typu środowiska wykonawczego obiektu.

Michael Myers
źródło
6
Jeśli masz zmienną o takiej samej nazwie jak nazwa w nadklasie i z jakiegoś powodu nie możesz (lub nie możesz) jej zmienić, nadal możesz uzyskać dostęp do zmiennej nadklasy o tej samej nazwie. Jaki jest pożytek, pytasz? Cóż ... Nigdy tego nie użyłem.
Michael Myers
Świetny link do dokumentu wyrażeń. Kilka dobrych przykładów dla osób uczących się do OCPJP.
Gordon,
Niesamowite. Nigdy bym nie pomyślał o użyciu obsady.
Thomas Eding,
dlaczego zmieniono klasę T1 na zmienną x? domyślnie jest to statyczny finał, prawda?
amarnath harish
@amarnathharish: To nie jest przesłonięte, a pola są domyślnie chronione przez pakiet i nie są ostateczne.
Michael Myers
41

Myślę, że następujący kod pozwala na użycie super.super ... super.method () w większości przypadków. (nawet jeśli jest to brzydkie)

W skrócie

  1. utwórz tymczasową instancję typu przodka
  2. skopiuj wartości pól z oryginalnego obiektu na tymczasowe
  3. wywołaj metodę docelową na obiekcie tymczasowym
  4. skopiuj zmodyfikowane wartości z powrotem do oryginalnego obiektu

Stosowanie :

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);
    }
}
Nico
źródło
10
Dzięki refleksji możesz zrobić wszystko, tak :) Możesz nawet zmienić łańcuchy znaków.
BalusC
23
Co za okropna rzecz! Daję Ci +1 za to, że w ogóle wiesz, jak to zrobić :)
Larry Watanabe
6
To fajna sztuczka, ale nawet to nie zawsze jest równoważne z wywołaniem nieosiągalnego, ale potrzebnego) super.super, a to dlatego, że wywołanie super.super przenosi kontekst C (C + B + A), podczas gdy twoje odpowiedzi tworzą instancję z A bez kontekstu B i C. Ta odpowiedź nie zadziała, jeśli każde doThat o nazwie getContext (), na przykład, i getContext zostało zaimplementowane inaczej w każdej klasie. w twojej odpowiedzi użyłby metody getContext () A, natomiast wywołanie niedostępnej super.super spowodowałoby użycie metody getContext C.
inor
Hmm W niektórych przypadkach być może uda się przezwyciężyć sprzeciw inora za pomocą dynamicznych serwerów proxy ( javahowto.blogspot.co.uk/2011/12/... ), przekierowując wywołania metod do oryginalnego obiektu (synchronizując zmienne po każdym wywołaniu)? Wydaje się jednak, że proxy wymagają wszystkiego, aby implementować interfejs. Zastanawiam się również, czy super-superklasa może wywołać jedną ze swoich super-super metod, a NIE musicie przekierowywać ich ...
Erhannis
W innym komentarzu powiedziałem, że blokowanie super.super.zachęca programistów do znalezienia nowych, zawiłych i skandalicznych sposobów strzelania sobie w stopę w celu obejścia tego problemu, jest to doskonały przykład tego, ponieważ koledzy prawdopodobnie będą cię tak bardzo nienawidzić za napisanie czegoś tak, że osobiście i dosłownie zastrzelą cię w stopę. +1
Braden Best
11

Nie mam wystarczającej reputacji, aby komentować, więc dodam to do innych odpowiedzi.

Jon Skeet odpowiada doskonale, z pięknym przykładem. Matt B ma rację: nie wszystkie superklasy mają supers. Twój kod pękłby, gdybyś nazwał super super, który nie miał super.

Programowanie obiektowe (którym jest Java) dotyczy obiektów, a nie funkcji. Jeśli chcesz programować zadania, wybierz C ++ lub coś innego. Jeśli twój obiekt nie mieści się w jego superklasie, musisz dodać go do „klasy dziadka”, utworzyć nową klasę lub znaleźć inną super-klasę, do której pasuje.

Osobiście uważam to ograniczenie za jedną z największych zalet Javy. Kod jest nieco sztywny w porównaniu do innych języków, których używałem, ale zawsze wiem, czego się spodziewać. Pomaga to w „prostym i znanym” celu Java. Moim zdaniem wywołanie super.super nie jest proste ani znajome. Być może programiści czuli to samo?

EllaJo
źródło
3
Mówisz: „nie wszystkie superklasy mają supersamochody”. Cóż, wszystko oprócz java.lang.Object, co może dać ci „null”. Powiedziałbym, że prawie wszystkie mają supers.
Tim Büthe,
Każda klasa, którą pisze programista, ma „super” (java.lang.Object nie, ale programista aplikacji tego nie pisze.)
finnw
2
Łatwo rozwiązany, czyniąc super.super.super ... super błędem czasu kompilacji, jeśli jest zbyt wiele superów. Biorąc pod uwagę, że Java ma tylko dziedziczenie publiczne, jeśli ktoś zmienił hierarchię dziedziczenia, zmieniasz interfejs. Więc nie martwiłbym się, że super ^ n jest przerażające.
Thomas Eding,
7

Jest kilka dobrych powodów, aby to zrobić. Możesz mieć podklasę, która ma niepoprawnie zaimplementowaną metodę, ale metoda nadrzędna jest zaimplementowana poprawnie. Ponieważ należy on do biblioteki innej firmy, możesz nie być w stanie / nie chcieć zmienić źródła. W takim przypadku chcesz utworzyć podklasę, ale zastąp jedną metodę, aby wywołać metodę super.super.

Jak pokazują niektóre inne plakaty, można to zrobić poprzez refleksję, ale powinno być możliwe zrobienie czegoś podobnego

(SuperSuperClass this) .theMethod ();

Mam teraz do czynienia z tym problemem - szybka poprawka polega na skopiowaniu i wklejeniu metody nadklasy do metody podklasy :)

Larry Watanabe
źródło
1
Rzutowanie przed wywołaniem metody nie zmienia metody, do której jest delegowana - zawsze używana jest implementacja podklasy, nigdy super. Zobacz np. Stackoverflow.com/questions/1677993/...
Joshua Goldberg
1
@ Larry to jest dokładnie taka sytuacja, w jakiej byłem, i dokładnie ta poprawka, której użyłem. Dobra decyzja!
bcr
Jeśli masz kod wymaganej metody, możesz otworzyć wszystkie potrzebne pola i uruchomić ten kod w swojej podklasie.
Enyby,
6

Oprócz bardzo dobrych uwag innych, myślę, że istnieje jeszcze jeden powód: co jeśli superklasa nie ma superklasy?

Ponieważ każda klasa naturalnie się rozszerza (przynajmniej) Object, super.whatever()zawsze będzie odnosić się do metody w nadklasie. Ale co, jeśli twoja klasa się przedłuży Object- do czego by super.supersię wtedy odnosiło? Jak należy obsługiwać to zachowanie - błąd kompilatora, wskaźnik NullPointer itp.?

Myślę, że głównym powodem, dla którego jest to niedozwolone, jest naruszenie enkapsulacji, ale może to być również niewielki powód.

matowy b
źródło
4
Oczywiście byłby to błąd kompilatora - i jest to informacja, którą posiada kompilator.
Michael Borgwardt,
4

Myślę, że jeśli nadpiszesz metodę i chcesz ją wszystkich w equalssuperklasycznej wersji (np. Powiedzmy za ), to praktycznie zawsze chcesz najpierw wywołać bezpośrednią wersję superklasy, która z kolei wywoła jej wersję superklasy, jeśli chce .

Myślę, że rzadko ma to sens (jeśli w ogóle. Nie mogę wymyślić przypadku, w którym tak się dzieje), aby wywołać dowolną wersję metody nadklasy. Nie wiem, czy jest to w ogóle możliwe w Javie. Można to zrobić w C ++:

this->ReallyTheBase::foo();
Johannes Schaub - litb
źródło
6
Ma to sens, jeśli ktoś napisał podklasę z jedną metodą nieprawidłowo zaimplementowaną, ale metoda nadklasy wykonuje 90% pracy. Następnie chcesz utworzyć podklasę i zastąpić metodę wywołującą metodę nadklasy superklasy i dodać własne 10%.
Larry Watanabe
3

Zgaduję, ponieważ nie jest tak często używany. Jedynym powodem, dla którego mogłem to zobaczyć, jest to, że twój bezpośredni rodzic zastąpił niektóre funkcje i próbujesz przywrócić je z powrotem do oryginału.

To wydaje mi się sprzeczne z zasadami OO, ponieważ bezpośredni rodzic klasy powinien być bliżej związany z twoją klasą niż dziadek.

Władca
źródło
3

Spójrz na ten projekt Github, zwłaszcza zmienna objectHandle. Ten projekt pokazuje, jak właściwie i dokładnie wywołać metodę dziadka u wnuka.

Na wypadek, gdyby link się zepsuł, oto kod:

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

Happy Coding !!!!

Kyay
źródło
Twoi naukowcy byli tak zajęci tym, czy mogą, ale nie zastanawiali się, czy powinni. Proszę, nie rób tego ...: P 🤔
Tim Büthe
Tak, to słowa kodera, nie moje. Moim zdaniem myślę, że może kiedyś tego potrzebujesz
Kyay
2

W miarę możliwości umieściłem treść metody super.super w innej metodzie

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

Lub jeśli nie możesz zmienić klasy super-super, możesz spróbować:

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

W obu przypadkach

new ChildClass().toString();

wyniki do „Jestem super super”

xMichal
źródło
Właśnie znalazłem się w sytuacji, w której posiadam SuperSuperClass i ChildClass, ale nie SuperClass, więc pierwsze rozwiązanie okazało się przydatne.
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);

     }
}

run: A BUILD SUCCESSFUL (całkowity czas: 0 sekund)

Boris
źródło
1
Rozumiem, ale tworzysz nową instancję, więc nie zadziałałoby, jeśli obiekt ma jakikolwiek stan.
Tim Büthe
1

Wywołanie super.super.method () ma sens, gdy nie można zmienić kodu klasy bazowej. Zdarza się to często podczas rozszerzania istniejącej biblioteki.

Najpierw zadaj sobie pytanie, dlaczego poszerzasz tę klasę? Jeśli odpowiedź brzmi „ponieważ nie mogę tego zmienić”, możesz utworzyć dokładny pakiet i klasę w swojej aplikacji oraz przepisać niegrzeczną metodę lub utworzyć delegata:

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() {...}
    ...
}

Na przykład możesz utworzyć klasę org.springframework.test.context.junit4.SpringJUnit4ClassRunner w swojej aplikacji, aby klasa ta została załadowana przed prawdziwą klasą ze słoika. Następnie przepisz metody lub konstruktory.

Uwaga: Jest to absolutny hack i zdecydowanie NIE zaleca się jego używania, ale DZIAŁA! Korzystanie z tego podejścia jest niebezpieczne ze względu na możliwe problemy z modułami ładującymi klasy. Może to również powodować problemy przy każdej aktualizacji biblioteki zawierającej zastąpioną klasę.

ruruskyi
źródło
1

Miałem takie sytuacje, gdy architektura ma budować wspólną funkcjonalność we wspólnej CustomBaseClass, która implementuje w imieniu kilku klas pochodnych. Musimy jednak obejść wspólną logikę dla konkretnej metody dla określonej klasy pochodnej. W takich przypadkach musimy użyć implementacji super.super.methodX.

Osiągamy to poprzez wprowadzenie elementu logicznego w CustomBaseClass, którego można użyć do selektywnego odroczenia niestandardowej implementacji i uzyskania domyślnej implementacji frameworka, gdy jest to pożądane.

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

Jednak dzięki dobrym zasadom architektury stosowanym zarówno w środowisku, jak i aplikacji, moglibyśmy łatwo uniknąć takich sytuacji, stosując podejście hasA zamiast podejścia isA. Ale przez cały czas nie jest zbyt praktyczne oczekiwanie na dobrze zaprojektowaną architekturę, a zatem potrzeba odejścia od solidnych zasad projektowania i wprowadzenia takich hacków. Tylko moje 2 centy ...

Ganesh Iyer
źródło
1

@Jon Skeet Ładne wyjaśnienie. IMO, jeśli ktoś chce wywołać metodę super.super, to musi chcieć zignorować zachowanie bezpośredniego rodzica, ale chce uzyskać dostęp do zachowania dziadka. Można to osiągnąć poprzez instancję. Jak poniżej kod

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

Oto klasa kierowców,

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

Wynik tego będzie

In C Class
In A Class

W tym przypadku zachowanie klasy B printClass zostanie zignorowane. Nie jestem pewien, czy jest to idealna lub dobra praktyka, aby osiągnąć super. Super, ale nadal działa.

Sanjay Jain
źródło
1
Cóż, to kreatywne, ale tak naprawdę nie odpowiada na moje pytanie. C nadal nie wywołuje super. Super, B po prostu zachowuje się inaczej. Jeśli możesz zmienić A i B, możesz po prostu dodać inną metodę zamiast używania instanceof. Super.super.foo pomógłby ci w przypadkach, gdy nie masz dostępu do A i B i nie możesz ich zmienić.
Tim Büthe,
Zgadzam się @ TimButhe, ale jeśli ktoś chce zadzwonić do super.super, to on / ona celowo chce zignorować zachowanie klasy nadrzędnej, więc wystarczy to osiągnąć poprzez istniejącą składnię java. (Dowolna z opcji, którą chcesz zastosować do instancji / innej metody)
Sanjay Jain,
0

Jeśli uważasz, że potrzebujesz superklasy, możesz odwoływać się do niej w zmiennej dla tej klasy. Na przykład:

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

Powinien wydrukować:

2
1
0
Popielniczka
źródło
2
Twój przykład jest trochę ... zły, ponieważ używasz metod statycznych. Podczas korzystania z metod statycznych nie potrzebujesz wcale zmiennej ani super. Może przegapiłeś tutaj podstawową koncepcję OO, przeczytaj ją. Przepraszam, muszę przegłosować.
Tim Büthe
1
Można to łatwo zrobić bez metod statycznych. To dość proste. Chciałem znaleźć prosty przykład, jak to zrobić.
Ashtheking
Rozumiem, że przechowywanie super zmiennej w polu jest jednym ze sposobów rozwiązania tego. Ale nie potrzebujesz zmiennej, jeśli jest to metoda statyczna, możesz po prostu jej użyć. Po drugie, wywoływanie metod statycznych dla jednej zmiennej jest złą praktyką i większość IDE ostrzega o tym. Jeśli naprawisz swoją odpowiedź, usuwając elementy statyczne, chętnie usunę mój głos negatywny, bez obrazy.
Tim Büthe
Jesteś blisko, ale twój kod się nie skompiluje. Próbujesz uzyskać dostęp do getNumber ze statycznego kontekstu w swojej głównej metodzie. Czy naprawdę próbowałeś to skompilować? (i czyż UltraFoo nie powinno rozszerzać SuperFoo?)
Tim Büthe
Naprawdę nie chcę być dla ciebie okrutny, ale new UltraFoo.getNumber()nie będę się kompilować, ponieważ przegapiłeś tam nawiasy. Jednak właśnie usunąłem moją donację, ponieważ koncepcja twojego kodu jest teraz całkiem jasna, dzięki!
Tim Büthe
0

IMO to czysty sposób na super.super.sayYourName()zachowanie w Javie.

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

Wynik:

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

Jakow Fain
źródło
ah, więc popierasz wdrożenie takiej hierarchii klas? Niestety, zaczyna się to robić naprawdę nieładnie, jeśli chcesz uzyskać dostęp do metody w Mamie od dziecka (podklasa córki) ...
bcr
Jakow Fain ma rację. innymi słowy, nie jest to dobry przykład, ponieważ pierwotne pytanie dotyczyło wywołania przesłoniętej metody w super.super.
inor
0

Myślę, że jest to problem, który łamie umowę spadkową.
Rozszerzając klasę, jesteś posłuszny / zgadzasz się na jego zachowanie, cechy
Podczas gdy podczas rozmowy super.super.method()chcesz złamać własną umowę posłuszeństwa.

Po prostu nie możesz wybrać z super klasy .

Mogą się jednak zdarzyć sytuacje, gdy poczujesz potrzebę połączenia super.super.method()- zwykle zły znak projektowy, w kodzie lub w odziedziczonym kodzie!
Jeśli super i super super klasy nie mogą być refaktoryzowane (niektóre starsze kody), to wybierz kompozycję zamiast dziedziczenia. Przerwanie

enkapsulacji ma miejsce, gdy @Opinie się nad niektórymi metodami przez zerwanie enkapsulowanego kodu. Metody, których nie można zastąpić, są oznaczone jako ostateczne .

DayaMoon
źródło
0

W języku C # możesz wywołać metodę dowolnego przodka takiego jak ten:

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

Możesz to również zrobić w Delphi:

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

Ale w Javie możesz skupić się tylko na niektórych urządzeniach. Jednym z możliwych sposobów jest:

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

wynik wyniku 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
D.Motyl
źródło
W języku C # będzie działać tylko w przypadku metod innych niż wirtualne, a ponieważ wszystkie metody są wirtualne w Javie, tak naprawdę nie jest inaczej.
Agent_L
0

To jest po prostu łatwe do zrobienia. Na przykład:

Podklasa C z podklasy B i B z A. Obie trzy mają na przykład metodę methodName ().

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

}

Uruchomienie klasy C Wyjście będzie: Klasa A Klasa C

Zamiast wyjścia: Klasa A Klasa B Klasa C

użytkownik2490562
źródło
-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();
    }
}

Wyjście: wydrukowano w GrandDad

Andrey
źródło
2
Ta odpowiedź jest poza zakresem pytania. OP nie zapytał, jak wywołać metodę w klasie dziadka. Problemem jest dyskusja, dlaczego super.super.method()nie jest prawidłowy kod w Javie.
Jed Schaaf,
-1

Słowo kluczowe super jest tylko sposobem na wywołanie metody w nadklasie. W samouczku Java: https://docs.oracle.com/javase/tutorial/java/IandI/super.html

Jeśli metoda zastępuje jedną z metod jej nadklasy, można wywołać zastąpioną metodę za pomocą słowa kluczowego super.

Nie wierz, że to odniesienie do super obiektu !!! Nie, to tylko słowo kluczowe do wywoływania metod w nadklasie.

Oto przykład:

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

Gdy zadzwonisz cat.doSth(), metoda doSth()w klasie Animalwydrukuje thisi jest to kot.

qing li
źródło