Java: вызов супер-метода, который вызывает переопределенный метод


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

мой ожидаемый результат:

подкласс method1
суперкласс method1
суперкласс method2

фактический выход:

подкласс метод1
суперкласс
метод1 подкласс метод2

Я знаю, что технически я переопределил общедоступный метод, но я подумал, что, поскольку я вызывал супер, любые вызовы внутри супер останутся в супер, этого не происходит. Есть идеи, как я могу это сделать?


2
Я подозреваю, что вы, возможно, захотите «предпочесть композицию наследованию».
Том Хотин - tackline

Ответы:


82

Ключевое слово superне «прилипает». Каждый вызов метода обрабатывается индивидуально, так что даже если вы получили SuperClass.method1()по телефону super, что не влияет на любой другой вызов метода , который вы могли бы сделать в будущем.

Это означает, что прямого вызова SuperClass.method2()из него SuperClass.method1()нет, если SubClass.method2()только вы не работаете с реальным экземпляром SuperClass.

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

[РЕДАКТИРОВАТЬ] По-прежнему существует некоторая путаница. Позвольте мне попробовать другое объяснение.

Когда вы вызываете 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, не могли бы вы подробнее про "Ява нет памяти на супер"?
MengT

@ Truman'sworld: как я сказал в своем ответе: использование superне меняет экземпляр. Он не устанавливает какое-то скрытое поле «с этого момента все вызовы методов должны начать использовать SuperClass». Или, другими словами: значение thisне меняется.
Аарон Дигулла

@AaronDigulla, значит ли это, что ключевое слово super фактически вызывает унаследованные методы в подклассе вместо перехода к суперклассу?
MengT 02

15

Вы можете получить доступ только к переопределенным методам в методах переопределения (или в других методах замещающего класса).

Итак: либо не переопределяйте, method2()либо вызывайте super.method2()внутри переопределенной версии.


8

Вы используете thisключевое слово, которое фактически относится к «запущенному в данный момент экземпляру объекта, который вы используете», то есть вы вызываете this.method2();свой суперкласс, то есть он вызовет метод2 () для объекта, который вы ' re using, который является подклассом.


9
правда, и неиспользование thisтоже не поможет. Неквалифицированный вызов неявно используетthis
Шон Патрик Флойд

3
Почему за это проголосовали? Это не ответ на этот вопрос. Когда вы напишете, method2()компилятор увидит this.method2(). Так что даже если вы удалите thisего, он все равно не сработает. То, что говорит @Sean Patrick Floyd, правильно
Шервин Асгари

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

4
Ответ правильный, указав, что это thisотносится к «конкретному классу запущенного экземпляра» (известному во время выполнения), а не (как, кажется, полагает автор) к «текущему классу единицы компиляции» (где используется ключевое слово, известное в время компиляции). Но это также может вводить в заблуждение (как указывает Шервин): на thisнего также неявно ссылаются при вызове простого метода; method2();совпадает сthis.method2();
leonbloy 04

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из суперкласса, вы получаете код из класса superduper [дедушка и бабушка], выполняемый), в то время как если вы используете this(или если он используется неявно) из суперкласса, он продолжает указывать на подкласс (потому что никто его не изменил - и никто мог).


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

вызов

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

выходы

подкласс method1
суперкласс method1
суперкласс method2


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 , я решил переместить метод 2 () из 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 .


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

Ключевое слово "this" относится к текущей ссылке на класс. Это означает, что когда он используется внутри метода, «текущий» класс по-прежнему является подклассом, поэтому ответ объясняется.


1

Подводя итог, это указывает на текущий объект, а вызов метода в java по своей природе полиморфен. Итак, выбор метода для выполнения полностью зависит от объекта, на который он указывает. Следовательно, вызов метода method2 () из родительского класса вызывает метод method2 () дочернего класса, так как this указывает на объект дочернего класса. Определение этого не меняется, независимо от того, какой класс он используется.

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

Я думаю, что вопрос о разрешении дела является разумным. Конечно, есть способы решить проблему с разными именами методов или даже разными типами параметров, как уже упоминалось в потоке, но в моем случае я не хочу путать разные имена методов.


Этот код опасен, рискован и дорог. Создание исключений требует, чтобы виртуальная машина построила полную трассировку стека, сравнение только по имени, а не по полной подписи чревато ошибками. Кроме того, он пахнет огромным недостатком дизайна.
M. le

С точки зрения производительности мой код, похоже, не оказывает большего влияния, чем new HashMap (). Size (). Однако я мог упустить из виду те проблемы, о которых вы думали, и я не являюсь экспертом в области виртуальных машин. Я вижу ваше сомнение, сравнивая имена классов, но он включает в себя пакет, что дает мне уверенность, что это мой родительский класс. В любом случае, мне нравится идея сравнения подписи, как бы вы это сделали? В общем, если у вас есть более плавный способ определить, является ли вызывающий суперклассом или кем-то еще, я был бы признателен за это здесь.
Зигриста

Если вам нужно определить, является ли вызывающий суперклассом, я бы серьезно подумал, если будет проведен редизайн. Это антипаттерн.
M. le

Я понимаю суть дела, но общая просьба ветки разумна. В некоторых ситуациях может иметь смысл вызов метода суперкласса оставаться в контексте суперкласса с любым вызовом вложенного метода. Однако, похоже, нет способа направить вызов метода в суперклассе соответствующим образом.
Зигриста

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.