Чому super.super.method (); заборонено на Java?


360

Я прочитав це питання і подумав, що його легко вирішити (не те, що без нього не вирішити), якби можна було написати:

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

Я не впевнений, чи корисний він у багатьох випадках, але мені цікаво, чому це не так, і чи щось подібне існує в інших мовах.

Як ви думаєте, хлопці?

EDIT: Для уточнення: так, я знаю, що в Java це неможливо, і я його дуже не сумую. Це я нічого не очікував, і я був здивований помилкою компілятора. У мене просто була ідея і я хотів її обговорити.


7
Бажаючи зателефонувати super.super.toString()суперечить вашому власному рішенню, коли ви вирішили розширити клас, таким чином приймаючи всі (не деякі) його особливості.
DayaMoon

Відповіді:


484

Це порушує інкапсуляцію. Ви не повинні мати можливість обійти поведінку батьківського класу. Має сенс іноді бути в змозі обійти поведінку власного класу (особливо, в рамках того ж методу), але не поведінку вашого батька. Наприклад, припустимо, у нас є базова "колекція предметів", підклас, що представляє "колекцію червоних предметів", і підклас групи, що представляє "колекцію великих червоних предметів". Має сенс:

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

Це добре - RedItems завжди може бути впевнений, що всі елементи, які він містить, є червоними. Тепер припустимо , що ми були в змозі назвати 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);
    }
}

Тепер ми можемо додати все, що нам подобається, і інваріант в роботі RedItemsзламаний.

Чи має це сенс?


38
приємний приклад. але я подумав, що це поганий дизайн, коли базовий клас приймає елементи, але похідний клас їх відкидає, тому що отриманий не може бути використаний як заміна базового класу (порушення принципу заміни). це правильне мислення, чи така ієрархія чиста?
Йоханнес Шауб - ліб

5
Ви маєте на увазі склад над спадщиною? Цей приклад не є причиною уникати спадкування, хоча інших існує багато.
Джон Скіт

3
@Tim: Це був просто зрозумілий приклад порушення інкапсуляції. Натомість це може бути встановлення властивості. Крім того, не всі аспекти будуть видні на рівні типу. Дженерики - це не відповідь на все.
Джон Скіт

12
@ JohannesSchaub-litb Я думаю, що це порушує принцип заміни Ліскова, оскільки якщо один кодує до договору Item і використовує екземпляр RedItems, ви отримаєте несподіване NotRedItemException. Мене завжди вчили, що підклас повинен приймати супер набір вхідних даних і повертати підмножину виводу. Іншими словами, підклас ніколи не повинен відхиляти вхід, який є дійсним для суперкласу або виробляти вихід, який суперклас не може виробляти, це включає помилку викидання, яку суперклас не кидає.
Костянтин Таращанський

4
@piechuckerr: Іноді книги, іноді повідомлення в блогах, іноді просто досвід ...
Джон Скіт

72

Я думаю, що Джон Скіт має правильну відповідь. Я хотів би лише додати, що ви можете отримати доступ до тіньових змінних із суперкласів надкласових класів шляхом кастингу 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();
        }
}

який виробляє вихід:

х = 3
super.x = 2
((T2) це) .x = 2
((T1) це) .x = 1
((I) це) .x = 0

(приклад з JLS )

Однак це не працює для викликів методів, оскільки виклики методів визначаються на основі типу часу виконання об'єкта.


6
Якщо у вас є змінна назва, що має те саме ім'я, що і в надкласі, і з якоїсь причини ви не можете (або заборонено) змінювати її, ви все одно можете отримати доступ до змінної суперкласу з тим же ім'ям. В чому користь, запитаєте ви? Ну ... я ніколи його не використовував.
Майкл Майерс

Відмінне посилання на документ із виразами. Кілька приємних прикладів для людей, які навчаються на OCPJP.
Гордон

Дивовижно. Я б ніколи не думав використовувати акторський склад.
Томас Едінг

звідки клас T1 замінює змінну x? його статичний фінал за замовчуванням, правда?
amarnath harish

@amarnathharish: Це не переосмислюється, а поля захищені пакетом не остаточні за замовчуванням.
Майкл Майєрс

41

Я думаю, що наступний код дозволяє в більшості випадків використовувати super.super ... super.method (). (навіть якщо це некрасиво робити це)

Коротко

  1. створити тимчасовий екземпляр типу предка
  2. скопіюйте значення полів з оригінального об'єкта в тимчасове
  3. викликати цільовий метод на тимчасовий об'єкт
  4. скопіювати змінені значення назад у вихідний об'єкт

Використання:

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
За допомогою роздумів ви можете зробити що завгодно, так :) Ви навіть можете зробити рядки незмінними.
BalusC

23
Що жахливого робити! Я даю вам +1 лише для того, щоб з’ясувати, як це зробити взагалі :)
Ларрі Ватанабе

6
Це приємний трюк, але навіть це не завжди рівнозначно виклику неприступного, але потрібного) super.super, і це тому, що виклик super.super несе контекст C (C + B + A), тоді як ваші відповіді створюють екземпляр з A без контексту B і C. Отже, ця відповідь не буде працювати, якщо, наприклад, кожен doT, який називається getContext (), і getContext реалізувались по-різному. у вашій відповіді він би використовував getContext (), тоді як виклик недоступного super.super призведе до використання getContext C.
inor

Хм. У деяких випадках, можливо, ви могли б подолати заперечення Інора за допомогою динамічних проксі-серверів ( javahowto.blogspot.co.uk/2011/12/… ), перенаправляючи виклики методу до вихідного об'єкта (синхронізуючи змінні після кожного дзвінка)? Здається, що проксі-сервери вимагають, щоб все було для реалізації інтерфейсу. Крім того, мені цікаво, чи можна супер-супер класу викликати один із його супер-супер методів, зокрема, і вам потрібно НЕ перенаправляти ці ....
Ерханніс

Я сказала в іншому коментарі, що блокування super.super.запрошує програмістів знайти нові, перекручені і нецензурні способи стріляти себе в ногу в пошуках обходу, це прекрасний приклад тому, тому що ваші колеги, напевно, вас так зненавиджуть, щоб написати щось як це, що вони особисто і буквально застрелять тебе в ногу. +1
Бреден Кращий

11

У мене недостатньо репутації для коментарів, тому я додам це до інших відповідей.

Джон Скіт відповідає чудово, прекрасним прикладом. У Метта Б є пункт: не всі суперкласи мають надповерхівки. Ваш код порушиться, якби ви назвали супер супер, у якого не було супер.

Об'єктно-орієнтоване програмування (якою є Java) - це все про об'єкти, а не функції. Якщо ви хочете програмування, орієнтоване на завдання, виберіть C ++ або щось інше. Якщо ваш об’єкт не входить до його суперкласу, то вам потрібно додати його до "класу бабусь і дідусів", створити новий клас або знайти інший супер, до якого він підходить.

Особисто я визнав це обмеження однією з найбільших сильних сторін Java. Код є дещо жорстким порівняно з іншими мовами, якими я користувався, але я завжди знаю, чого очікувати. У цьому допомагає «проста і знайома» мета Java. На мій погляд, дзвонити в super.super не просто і не звично. Можливо, розробники відчували те саме?


3
Ви кажете, "не у всіх суперкласах є супер". Ну, окрім java.lang.Object що може дати вам "null". Тому я б сказав, майже у всіх є надповерхівки.
Тім Бют

Кожен клас, який пише програміст програми, має "супер" (java.lang.Object не робить, але програміст програми цього не пише.)
finnw

2
Легко вирішується, роблячи super.super.super ... супер помилка часу компіляції, якщо надто багато суперів. Зважаючи на те, що Java має лише публічне успадкування, якщо хтось змінив ієрархію спадкування, ви змінюєте інтерфейс. Тож я б не переживав, що супер ^ n страшно.
Томас Едінг

7

Для цього є вагомі причини. У вас може бути підклас, який має метод, який реалізований неправильно, але батьківський метод реалізований правильно. Оскільки він належить до бібліотеки третьої сторони, можливо, ви не можете / не бажаєте змінювати джерело. У цьому випадку потрібно створити підклас, але замінити один метод для виклику методу super.super.

Як показано на деяких інших плакатах, це можна зробити за допомогою рефлексії, але слід зробити щось подібне

(SuperSuperClass це) .theMethod ();

Я зараз маю справу з цією проблемою - швидке виправлення полягає в копіюванні та вставці методу надкласу в метод підкласу :)


1
Кастинг перед викликом методу не змінює метод, який делегований - завжди використовується реалізація підкласу, ніколи не супер. Дивіться, наприклад, stackoverflow.com/questions/1677993/…
Джошуа Голдберг

1
@Larry - це саме така ситуація, в якій я був, і саме те виправлення, яке я використав. Гарний дзвінок!
bcr

Якщо у вас є код потрібного методу, ви можете відкрити всі необхідні поля та запустити цей код у своєму підкласі.
Енібі

6

Окрім дуже хороших моментів, які зробили інші, я думаю, є ще одна причина: що робити, якщо у суперкласу немає суперкласу?

Оскільки кожен клас природно розширюється (принаймні) Object, super.whatever()завжди буде посилатися на метод у надкласі. Але що робити, якщо ваш клас тільки поширюється Object- на що б super.superтоді говорилося? Як слід поводитися з такою поведінкою - помилка компілятора, NullPointer тощо?

Я думаю, що головна причина, чому це не дозволено, полягає в тому, що це порушує інкапсуляцію, але це може бути і малою причиною.


4
Очевидно, це була б помилка компілятора - і це інформація, яку має компілятор.
Майкл Боргвардт

4

Я думаю, якщо ви перезапишете метод і хочете отримати всі версії суперкласового його типу (як, скажімо, для equals версію суперкласу ), то ви завжди завжди хочете спершу викликати пряму версію суперкласу, яка називатиме свою версію надкласового якщо вона хоче .

Я думаю, що лише рідко має сенс (якщо взагалі я не можу придумати випадок, коли він є) викликати версію методу довільного суперкласу. Я не знаю, чи це взагалі можливо на Java. Це можна зробити на C ++:

this->ReallyTheBase::foo();

6
Це має сенс, якщо хтось написав підклас з одним методом, реалізованим неправильно, але метод надкласового класу складає 90% роботи. Потім ви хочете скласти підклас і замінити метод виклику методу надкласу надкласу та додати свої власні 10%.
Ларрі Ватанабе

3

Здогадуюсь, тому що він використовується не так часто. Єдина причина, яку я міг бачити, використовуючи його, - це те, якщо ваш прямий батько переосмислив певну функціональність і ви намагаєтесь відновити його до оригіналу.

Мені здається, це суперечить принципам ОО, оскільки прямий батько класу повинен бути тісніше пов'язаний з вашим класом, ніж це бабуся і дідусь.


3

Подивіться на цей проект Github, особливо змінну objectHandle. Цей проект показує, як насправді та точно називати метод діда на онука.

На всякий випадок, коли посилання розірветься, ось код:

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

Щасливе кодування !!!!


Ваші вчені були настільки зайняті тим, чи можуть вони, чи ні, вони не переставали думати, чи варто. Будь ласка, насправді цього не робіть ...: P 🤔
Тім Бют

Так, це слова кодера, а не мої. На мою думку, я думаю, що вам справді це може знадобитися колись
kayay

2

Я б поклав тіло методу super.super в інший метод, якщо це можливо

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

Або якщо ви не можете змінити клас супер-супер, ви можете спробувати це:

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

В обох випадках

new ChildClass().toString();

результати до "Я супер супер"


Я просто опинився в ситуації, коли мені належать SuperSuperClass та ChildClass, але не SuperClass, тому я знайшов перше рішення корисним.
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);

     }
}

запуск: БУДІВНИЙ УСПІШНИЙ (загальний час: 0 секунд)


1
Я бачу, але ви створюєте новий екземпляр, щоб це не спрацювало, якщо об’єкт має стан.
Тім Бют

1

Виклик super.super.method () має сенс, коли ви не можете змінити код базового класу. Це часто трапляється, коли ви розширюєте наявну бібліотеку.

Спершу запитайте себе, чому ви розширюєте цей клас? Якщо відповідь "тому, що я не можу її змінити", ви можете створити точний пакет і клас у своїй програмі та переписати неслухняний метод або створити делегат:

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

Наприклад, ви можете створити org.springframework.test.context.junit4.SpringJUnit4ClassRunner клас у вашому додатку, тому цей клас слід завантажувати перед реальним з банку. Потім перепишіть методи або конструктори.

Увага: Це абсолютний хак, і його настійно НЕ рекомендується використовувати, але це РОБОТА! Використання такого підходу небезпечно через можливі проблеми з навантажувачами класу. Також це може спричинити проблеми щоразу, коли ви будете оновлювати бібліотеку, що містить перезаписаний клас.


1

У мене були такі ситуації, коли архітектура повинна будувати загальну функціональність у загальному CustomBaseClass, який реалізує від імені кількох похідних класів. Однак нам потрібно обійти загальну логіку для конкретного методу для конкретного похідного класу. У таких випадках ми повинні використовувати супер.super.methodX реалізацію.

Ми досягаємо цього, вводячи бульовий член у CustomBaseClass, який може бути використаний для вибіркового відстрочення користувацької реалізації та приведення до виконання рамки за замовчуванням, де бажано.

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

Однак, з хорошими архітектурними принципами, які дотримуються як в рамках, так і в застосуванні, ми могли б уникнути подібних ситуацій легко, використовуючи hasA підхід, а не підхід isA. Але в будь-який час не дуже практично розраховувати на добре продуману архітектуру, а отже, потрібно піти від твердих принципів дизайну та запровадити подібні хаки. Тільки мої 2 копійки ...


1

@Jon Skeet Приємне пояснення. ІМО, якщо хтось хоче викликати метод super.super, тоді потрібно ігнорувати поведінку безпосереднього батька, але хоче отримати доступ до поведінки батьків. Цього можна досягти за допомогою примірника Of. Як показано нижче коду

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

Ось клас водія,

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

Результат цього буде

In C Class
In A Class

В цьому випадку поведінка printClass класу B буде ігноровано. Я не впевнений, це ідеальна чи хороша практика для досягнення супер.супера, але все-таки це працює.


1
Ну, це творчо, але насправді не відповідає на моє запитання. C як і раніше не називає super.super, B просто поводиться інакше. Якщо ви можете змінити A і B, ви можете просто додати інший метод замість використання instanceof. Super.super.foo допоможе вам у випадках, коли ви не маєте доступу до A і B і не можете їх змінити.
Тім Бют

Погодьтеся @TimButhe, але якщо хтось хоче зателефонувати на super.super, тоді він / вона навмисно хочуть ігнорувати поведінку батьківського класу, тож вам просто потрібно досягти цього за існуючим синтаксисом Java. (Будь-який із варіантів, який ви хочете, або instanceof / different method)
Sanjay Jain,

0

Якщо ви думаєте, що вам знадобиться суперклас, ви можете посилатися на нього у змінній для цього класу. Наприклад:

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

Слід роздрукувати:

2
1
0

2
Ваш приклад є начебто ... добре, тому що ви використовуєте статичні методи. Використовуючи статичні методи, вам не потрібна змінна або супер. Можливо, тут ви пропустили якусь базову концепцію ОО, не забудьте прочитати про це. Доводиться зволікати, вибачте.
Тім Бют

1
Це можна було легко зробити без статичних методів. Це досить просто. Я збирався простий приклад, як це зробити.
Ashtheking

Я розумію, зберігання супер змінної у полі - це один із способів вирішити це. Але вам не потрібна змінна, якщо це статичний метод, ви можете просто використовувати її. По-друге, використання статичних методів однієї змінної є поганою практикою, і більшість IDE попередить вас про це. Якщо ви виправите свою відповідь, видаляючи статичні речі, я буду радий зняти свій голос, без образи.
Тім Бют

Ви близькі, але ваш код не буде компілюватися. Ви намагаєтеся отримати доступ до getNumber зі статичного контексту в своєму основному методі. Ви насправді намагалися це скласти? (а чи не повинні ваш UltraFoo розширити SuperFoo?)
Тім Бют

Я дійсно не хочу бути жорстоким з вами, але new UltraFoo.getNumber()не буду складати, оскільки ви пропустили там круглі дужки. Однак я просто вилучив свій донот, оскільки концепція вашого коду зараз досить зрозуміла, дякую!
Тім Бют

0

IMO, це чистий спосіб досягти super.super.sayYourName()поведінки на 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);
    }
}

Вихід:

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


ах, значить, ви виступаєте за реалізацію подібної ієрархії класів? На жаль, це стає по-справжньому безладним, якщо ви хочете отримати доступ до методу у мами від немовляти (підклас дочки) ...
bcr

Яків файн прав. Іншими словами, це не гарний приклад, оскільки оригінальне питання стосувалося виклику перекритого методу у super.super.
inor

0

Я думаю, це проблема, яка порушує спадковий договір.
Розширюючи клас, ви підкоряєтесь / погоджуєтесь з його поведінкою, особливостями
Під час дзвінка super.super.method()ви хочете розірвати власну угоду про послух.

Ви просто не можете вибрати вишню з суперкласу .

Однак можуть траплятися ситуації, коли ви відчуваєте необхідність дзвонити super.super.method()- зазвичай це поганий знак оформлення, у вашому коді чи в коді, який ви успадковуєте!
Якщо супер та супер суперкласи не вдається відновити ремонт (деякий застарілий код), тоді вибирайте склад над успадкуванням.

Порушення інкапсуляції - це коли ви @Override деякі методи, порушивши інкапсульований код. Методи, розроблені таким чином, що їх не можна перекривати, позначаються остаточними .


0

У C # ви можете назвати метод будь-якого предка, як це:

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

Також ви можете це зробити в Delphi:

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

Але на Java ви можете робити таке фокусування лише деякими передачами. Один з можливих способів:

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

Вихід результату 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

У C # він працюватиме лише для невіртуальних методів, і оскільки всі методи є віртуальними в Java, він насправді не відрізняється.
Agent_L

0

Зробити це просто просто. Наприклад:

Підклас C підкласу B і B А. Наприклад, обидва з трьох мають метод 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");
    }

}

Виконати клас C Вихід буде: Клас A Клас 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();
    }
}

Вихід: Надрукований у GrandDad


2
Ця відповідь виходить за межі питання. ОП не запитували, як викликати метод у класі дідуся та бабусі. Проблема полягає в обговоренні того, чому super.super.method()в Java не дійсний код.
Джед Шааф

-1

Ключове слово super - це лише спосіб викликати метод у надкласі. У навчальному посібнику Java: https://docs.oracle.com/javase/tutorial/java/IandI/super.html

Якщо ваш метод переосмислює один із методів його надкласового класу, ви можете викликати метод переосмисленості за допомогою ключового слова super.

Не вірте, що це посилання на супероб'єкт !!! Ні, це лише ключове слово, щоб викликати методи у надкласі.

Ось приклад:

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

Коли ви телефонуєте cat.doSth(), метод doSth()у класі Animalбуде друкувати, thisі це кішка.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.