Почему method () и super.method () ссылаются на разные вещи в анонимном подклассе? - PullRequest
0 голосов
/ 03 июля 2018

Я решал некоторые упражнения, чтобы лучше понять, как работают внутренние классы в Java. Я нашел одно довольно интересное упражнение. Условие упражнения - сделать printName() печать «sout» вместо «main» с минимальными изменениями. Есть его код:

public class Solution {
    private String name;

    Solution(String name) {
        this.name = name;
    }

    private String getName() {
        return name;
    }

    private void sout() {
        new Solution("sout") {
            void printName() {
                System.out.println(getName());
                // the line above is an equivalent to:
                // System.out.println(Solution.this.getName);
            }
        }.printName();
    }

    public static void main(String[] args) {
        new Solution("main").sout();
    }
}

У нас забавная ситуация - у двух классов есть соединения is-A и has-A. Это означает, что анонимный внутренний класс расширяет внешний класс, а также объекты внутреннего класса имеют ссылки на объекты внешнего класса. Если вы запустите код выше, будет напечатано «main». Ребенок не может вызвать getName() родителя через наследование. Но дочерний объект, являющийся внутренним классом, использует ссылку на parent (внешний класс) для доступа к методу.

Самый простой способ решить эту задачу - изменить модификатор доступа getName() с private на любой другой. Таким образом, ребенок может использовать getName() посредством наследования, и благодаря позднему связыванию будет напечатан «sout».

Другой способ решить эту задачу - использовать super.getName().

private void sout() {
    new Solution("sout") {
        void printName() {
            System.out.println(super.getName());
        }
    }.printName();
}

И я не могу понять, как это работает. Может ли кто-нибудь помочь мне понять эту проблему?

Спасибо за попытку)

Ответы [ 2 ]

0 голосов
/ 03 июля 2018

Поведение может показаться нелогичным, но это становится понятным после небольшого рефакторинга.

Итак, метод sout() может быть переписан как

private void sout() {
  new Solution("sout") {
    void printName() {
      String name = getName();
      System.out.println(name);
    }
  }.printName();
}

public static void main(String[] args) {
  Solution mainSolution = new Solution("main");
  mainSolution.sout();
}

Вызов метода sout() объекта mainSolution создает дочерний объект Solution с дополнительным методом printName(), который вызывает

getName();

который только объявлен в родительском mainSolution объекте.

Если getName() объявлен как закрытый, он не переопределяется, но все еще доступен из внутреннего класса, поэтому getName() относится к имени mainSolution, то есть к main.

Если getName() не имеет модификатора или объявлен как защищенный или общедоступный, то он наследуется (переопределяется) и ссылается на имя дочернего объекта Solution, то есть на sout, поэтому будет напечатано «sout» .

Заменив getName() в sout() на

Solution.this.getName()

строка «main» будет напечатана в обоих сценариях.

Заменив его на любой из

this.getName()
super.getName()

если метод getName() объявлен как private, произойдет ошибка компиляции, в противном случае будет напечатана строка "sout".

0 голосов
/ 03 июля 2018

Спецификация языка Java (JLS), в контексте компилятора, разрешающего выражение вызова метода , состояния

Если форма super . [TypeArguments] Identifier, то класс поиск является суперклассом класса, объявление которого содержит вызов метода.

Класс , в объявлении которого содержится вызов метода , в данном случае является анонимным подклассом Solution, а его суперкласс - Solution. JLS в контексте определения того, какой экземпляр будет использоваться для вызова метода , затем говорит:

Если форма super . [TypeArguments] Identifier, то цель ссылка является значением this.

this, в данном случае, относится к экземпляру анонимного подкласса Solution. Поле name этого экземпляра было инициализировано значением "sout", поэтому getName() возвращает.


В исходном образце

new Solution("sout") {
    void printName() {
        System.out.println(getName());
    }
}.printName();

вызов метода getName() является неквалифицированным, и поэтому применяется другое правило. Это

Если существует объявление типа, для которого этот метод является член, пусть T будет самым внутренним объявлением типа. Класс или интерфейс для поиска T.

T здесь - это класс Solution, поскольку он является самым внутренним типом включения анонимного подкласса Solution, а getName() является его членом.

Тогда JLS сообщает

В противном случае, пусть T будет объявлением окружающего типа, для которого метод является членом, и пусть n будет целым числом таким, что T является n-ным лексическим вложение декларации типа класса, объявление которого немедленно содержит вызов метода. Целевой ориентир - это n'th Лексически заключенный экземпляр this.

Опять же, T - это Solution, 1-й лексически включающий тип, поскольку класс, в объявлении которого непосредственно содержится вызов метода, является анонимным подклассом Solution. this - это анонимный экземпляр подкласса Solution. Таким образом, целевой ссылкой является 1-й лексически заключенный экземпляр this, т.е. экземпляр Solution, поле которого name было инициализировано значением "main". Вот почему оригинальный код печатает "main".

...