Java "трюк", переопределение дочернего класса - PullRequest
15 голосов
/ 15 мая 2011

Я готовлюсь к экзамену по Java и столкнулся с тем, чего не понимаю в предмете прошлого года.Вот код

class Mother {
    int var = 2;

    int getVar() {
        return var;
    }
}

class Daughter extends Mother {
    int var = 1;

    int getVar() { 
        return var;
    }

    public static void main(String[] args) {
        Mother m = new Mother();
        System.out.println(m.var);
        System.out.println(m.getVar());
        m = new Daughter();
        System.out.println(m.var);
        System.out.println(m.getVar());
    }
}

Вопрос в том, "каков вывод этой программы?".Я бы пошел с 2 2 1 1, но при компиляции и запуске этого фрагмента кода я получаю 2 2 2 1.

Кто-нибудь может объяснить мне, почему?1008 *

Ответы [ 6 ]

16 голосов
/ 15 мая 2011

Вызов метода m.getVar() - это вызов виртуального метода.Во второй раз, когда вы вызываете его, он динамически отправляется производному Daughter.getVar(), который делает то, что вы ожидаете (обращается к Daugther.var и возвращает это).

Для полей-членов такого механизма виртуальной отправки не существует.Таким образом, m.var всегда относится к Mother.var, то есть версии этой переменной базового класса.

Класс Daughter можно рассматривать как имеющий два различных члена var: один из Mother исвой.Его собственный член «скрывает» член в Mother, но к нему можно получить доступ из класса Daughter, используя super.var.

Официальная спецификация для этого находится в разделе 8.3 Объявления полей из JLS .Цитата:

Если класс объявляет поле с определенным именем, то объявление этого поля называется hide любым доступным объявлением полей с одинаковым именем всуперклассы и суперинтерфейсы класса.Объявление поля также скрывает (§6.3.1) объявления любых доступных полей во вложенных классах или интерфейсах, а также любых локальных переменных, параметров формальных методов и параметров обработчика исключений с одинаковыми именами во всех включающих блоках.

Обратите внимание, что это может быть довольно интересно (выделение добавлено):

Если объявление поля скрывает объявление другого поля, два поля не обязательно должны иметь одинаковый тип .

И:

Может быть несколько путей, по которым одно и то же объявление поля может быть унаследовано от интерфейса .В такой ситуации поле считается унаследованным только один раз, и его можно назвать простым именем без двусмысленности.

Так что этот абзац стоит прочитать: -)

6 голосов
/ 15 мая 2011

Сфокусируйтесь на этих строках:

Mother m;
 m = new Daughter();
 System.out.println(m.var);
 System.out.println(m.getVar());

Вы создаете объект Daughter, но относитесь к нему, как к базовому классу Mother.Поэтому, когда вы обращаетесь к m.var, вы обращаетесь к переменной базового класса var.Между тем, когда вы вызываете метод, даже если вы ссылаетесь на ссылку на базовый класс, вызывается переопределенный метод.Это другое поведение для методов и полей. Ссылка на поля не может быть переопределена.

2 голосов
/ 15 мая 2011

Методы могут быть переопределены, однако поля могут быть только скрыты.Разница в том, что нестатический метод использует тип объекта, на который ссылаются, поле принимает тип ссылки.Вы видите аналогичную вещь со статическими методами, которые скрыты только в том случае, если класс «ссылки» и объекта (если имеется) игнорируется.

Для вашего интереса попробуйте задать поля различных типов.;)

Вы также можете попробовать

System.out.println(((Mother)m).var); // uses var in Mother
System.out.println(((Daughter)m).var); // uses var in Daughter
1 голос
/ 15 мая 2011
m = new Daughter();

Несмотря на то, что вы создали объект daughter, вы ссылаетесь на этот объект со ссылкой Mother m.Таким образом, любой вызов, использующий m, вызовет членов класса Матери, а не

дочери.
0 голосов
/ 06 июня 2011

Я прочитал ответы, и ни один из них (пока) не дал веских оснований, почему в объектно-ориентированном языке, как Java, это должно быть так. Я постараюсь объяснить.

Предположим, у вас есть функция, которая принимает Мать как arg:

void foo(Mother m) {
  print(m.var);
}

Эта функция (фактически компилятор) понятия не имеет, вызовете ли вы ее с помощью Mother, Daughter или другой Dauther2, в которой даже не объявлена ​​переменная var . Из-за этого, когда ссылка имеет тип Mother, ссылка на переменную-член должна быть связана (компилятором) с членом Mother. То же самое относится и к функции , поэтому функции связаны с объявлением Матери из getVar(), но не с Mother * реализация из getVar()

Таким образом, переменные-члены всегда связаны (компилятором) на основе ссылки. Еще один способ объяснить это: если вы удалите Mother var (и сделаете Mother getVar() скомпилируемым), ваша second m.var (когда m относится к Daughter) не скомпилирует даже Daughter имеет член var.

Надеюсь, я был чист.

0 голосов
/ 15 мая 2011

Я запустил это в Eclipse и проверил значения с помощью отладчика, отладчик фактически показывает локальную переменную m, имеющую ДВУХ различных переменных-членов после строки m = new Daugher() со значениями 2 и 1. Кажется, что m.varпреобразуется в метод Mother, и m.getVar () вызывает метод getVar в Daughter (как и ожидалось).

Однако, когда я изменяю метод main, чтобы он выглядел так:

    Mother m = new Mother();
    System.out.println(m.var);
    System.out.println(m.getVar());
    Daughter d = new Daughter();
    System.out.println(d.var);
    System.out.println(d.getVar());

На самом деле выводит 2, 2, 1, 1, поэтому может показаться, что объявление переменной влияет на то, какой класс var используется.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...