Java: виклик супер методу, який викликає переопределений метод


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

мій очікуваний вихід:

метод підкласу1 метод
надкласу1 метод
надкласового класу2

фактичний вихід:

метод підкласу1 метод
надкласового класу1 метод
підкласу2

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


2
Я підозрюю, що ви можете "віддати перевагу композиції над спадщиною".
Том Хотін - тайклін

Відповіді:


79

Ключове слово superне "прилипає". Кожен виклик методу обробляється індивідуально, так що навіть якщо ви отримаєте SuperClass.method1()виклик super, це не впливає на будь-який інший виклик методу, який ви можете зробити в майбутньому.

Це означає , що не існує прямий спосіб виклику SuperClass.method2()з SuperClass.method1()не заходячи , хоча , SubClass.method2()якщо ви не працюєте з реальним екземпляром SuperClass.

Ви навіть не можете досягти бажаного ефекту за допомогою Reflection (див . Документаціюjava.lang.reflect.Method.invoke(Object, Object...) ).

[EDIT] Все ще здається певна плутанина. Дозвольте спробувати інше пояснення.

Коли ви викликаєте foo(), ви фактично викликаєте this.foo(). Java дозволяє просто опустити this. У прикладі у питанні тип thisє SubClass.

Отже, коли Java виконує код у SuperClass.method1(), він, врешті-решт, приходить доthis.method2();

Використання superне змінює екземпляр, на який вказує this. Таким чином, виклик направляється SubClass.method2()так thisце типу SubClass.

Можливо, це легше зрозуміти, коли ти уявляєш, що Java проходить thisяк прихований перший параметр:

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

Якщо ви стежите за стеком викликів, ви можете бачити, що thisніколи не змінюється, це завжди екземпляр, створений в main().


хтось може, будь ласка, завантажити схему цього (призначений для каламбура), який проходить через стек? Спасибі заздалегідь!
laycat

2
@laycat: Діаграма не потрібна. Пам'ятайте лише, що Java не має "пам'яті" для super. Кожен раз, коли він викликає метод, він перегляне тип екземпляра та почне шукати метод із цим типом, незалежно від того, як часто ви викликали super. Тож, коли ви зателефонуєте method2до екземпляра SubClass, він завжди буде бачити той, хто SubClassспочатку.
Аарон Дігулла

@AaronDigulla, Чи можете ви пояснити більше про "Java не має пам'яті для супер"?
MengT

@ Truman'sworld: як я вже сказав у своїй відповіді: використання superне змінює екземпляр. У ньому не встановлено приховане поле "відтепер усі виклики методу повинні почати використовувати SuperClass". Або кажучи інакше: значення thisне змінюється.
Аарон Дігулла

@AaronDigulla, значить, це означає, що супер ключове слово насправді викликає успадковані методи в підкласі замість переходу до суперкласу?
MengT

15

Ви можете отримати доступ до перекритих методів лише в методах переосмислення (або в інших методах класу переосмислення).

Отже: або не переосмислюйте, method2()і не дзвоніть super.method2()всередині перекритої версії.


8

Ви використовуєте thisключове слово, яке насправді посилається на "поточно запущений екземпляр об'єкта, який ви використовуєте", тобто ви посилаєтесь this.method2();на свій суперклас, тобто він буде викликати метод2 () на об'єкті, який ви ' повторне використання, що є підкласом.


8
правда, і не використовувати thisтакож не допоможе. Некваліфіковане виклик неявно використовуєthis
Шон Патрік Флойд

3
Чому це сприйняття? Це не відповідь на це питання. Коли ви пишете, method2()компілятор побачить this.method2(). Так що навіть якщо ви виймете thisйого, все одно не вийде. Те, що говорить @Sean Патрік Флойд, є правильним
Шервін Асгарі

4
@Shervin він не каже нічого поганого, він просто не дає зрозуміти, що трапиться, якщо ви вийдетеthis
Шон Патрік Флойд

4
Відповідь правильна, вказуючи, що thisйдеться про "конкретний клас екземпляра, який працює (відомий під час виконання), а не (як вважає плакат)" поточний клас одиниці компіляції "(де використовується ключове слово, відоме в час складання). Але це також може вводити в оману (як вказує Шервін): thisтакож посилається неявно на звичайний виклик методу; method2();те саме, щоthis.method2();
leonbloy

7

Я думаю про це так

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

Дозвольте перенести цей підклас трохи ліворуч, щоб виявити, що знаходиться внизу ... (Людина, я люблю графіку 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).

Іншими словами, цитуючи специфікацію мови Java :

Форма super.Identifierпосилається на поле, назване Identifierпоточним об'єктом, але з поточним об'єктом розглядається як екземпляр надкласу поточного класу.

Форма T.super.Identifierпосилається на поле з ім'ям Identifierлексично огороджувального екземпляра, що відповідає T, але з цим екземпляром розглядається як екземпляр надкласу T.

З точки зору неспеціаліста, thisце в основному об'єкт (* об’єкт **; той самий об’єкт, по якому можна переміщатися в змінних), екземпляр інстанційованого класу, звичайна змінна в області даних; superце як вказівник на запозичений блок коду, який ви хочете виконати, більше нагадує простий функціональний виклик, і це відносно класу, куди він викликається.

Тому, якщо ви використовуєте superз суперкласу, ви отримуєте код з класу супердупер [бабуся і дідусь], який виконується), а якщо ви використовуєте this(або якщо він використовується неявно) з надкласу, він продовжує вказувати на підклас (тому що ніхто його не змінив - і ніхто міг).


2

Якщо ви не хочете, щоб superClass.method1 викликав subClass.method2, зробіть method2 приватним, щоб його не можна було перекрити.

Ось пропозиція:

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

Якби це не працювало таким чином, поліморфізм був би неможливим (або, принаймні, не наполовину кориснішим).


2

Оскільки єдиним способом уникнути перевиконання методу є використання ключового слова super , я подумав перейти на method2 () з SuperClass до іншого нового базового класу, а потім викликати його з 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();
    }
}

Вихід:

subclass method1
superclass method1
superclass method2

2

this завжди посилається на об'єкт, що виконує в даний час.

Для подальшої ілюстрації тут є простий ескіз:

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

Якщо у вас є екземпляр зовнішньої коробки, Subclassоб'єкта, де б ви не потрапили всередину коробки, навіть у Superclass"область", це все ще екземпляр зовнішньої коробки.

Більше того, у цій програмі є лише один об'єкт, який створюється з трьох класів, тому thisможе колись посилатися лише на одне, і це:

введіть тут опис зображення

як це показано в Netbeans "Heap Walker".


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

дзвінок

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

виходи

метод підкласу1 метод
надкласу1 метод
надкласового класу2


1

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

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

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

}

1

Ключове слово "це" посилається на поточну посилання на клас. Це означає, що коли він використовується всередині методу, клас "current" все ще є SubClass, і тому відповідь пояснюється.


1

Підсумовуючи це, це вказує на поточний об'єкт, а виклик методу в java є поліморфним за своєю природою. Отже, вибір методу для виконання повністю залежить від об'єкта, вказаного цим. Тому виклик методу method2 () з батьківського класу викликає method2 () дочірнього класу, оскільки це вказує на об'єкт дочірнього класу. Визначення цього не змінюється, незалежно від того, який клас він використовується.

PS. на відміну від методів, змінні члена класу не є поліморфними.


0

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

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

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


Цей код небезпечний, ризикований і дорогий. Створення винятків вимагає від VM побудувати повний стек-трек, порівнюючи лише ім'я, а не повний підпис, схильний до помилок. Крім того, він відчуває величезний недолік дизайну.
M. le Rutte

З точки зору продуктивності, мій код, здається, не справляє більшого впливу, ніж "новий HashMap (). Size ()". Однак, можливо, я не помітив побоювань, про які ви думали, і я, по праву, не експерт з ВМ. Я бачу ваші сумніви, порівнюючи назви класів, але він включає пакет, в чому я впевнений, це мій батьківський клас. У будь-якому випадку мені подобається Ідея порівняння підпису, а як би ти це зробив? Взагалі, якщо у вас є більш гладкий спосіб визначити, чи є абонент суперкласу або хтось інший, я би вдячний тут.
Збийте Зігриста

Якщо вам потрібно визначити, чи є абонент суперкласу, я б серйозно подумав довше, якщо буде перероблений дизайн. Це анти-модель.
M. le Rutte

Я бачу сенс, але загальний запит потоку є розумним. У деяких випадках може мати сенс виклик методу надкласу залишається в контексті надкласу з будь-яким вкладеним викликом методу. Однак, схоже, немає можливості направити відповідний виклик методу відповідно до Суперкласу.
Збийте Зігриста

0

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

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