#java
#java
Вопрос:
Вероятно, где-то есть ответ, но я понятия не имею, что искать. Представьте, что у вас есть следующее…
Суперкласс, Animal.java
public class Animal {
public String noise = "squeak";
public String toString() { return noise; }
}
Подкласс, Lion.java
public class Lion extends Animal {
public String noise = "ROAR!!";
public String toString() { return noise; }
}
Основной класс, Runner.java
public class Runner {
public static void main(String[] args) {
Animal a = new Animal();
System.out.println(a);
System.out.println(a.noise);
Lion b = new Lion();
System.out.println(b);
System.out.println(b.noise);
Animal c = new Lion();
System.out.println(c);
System.out.println(c.noise);
}
}
Результат таков:
squeak
squeak
ROAR!!
ROAR!!
ROAR!!
squeak
Почему c.noise возвращает писк? В чем разница между вызовом метода экземпляра и переменными экземпляра, что один возвращает то, что вы ожидаете, а другой нет? Почему Java это делает?
Спасибо
Комментарии:
1. Я не Java pro, поэтому не буду называть это ответом, но разница в том, что методы в Java являются виртуальными. То есть, когда вы вызываете c.toString(), Java с помощью таблицы подстановки знает, что правильный метод для использования — Lion’s toString(). Однако эта виртуализация не выполняется для переменных экземпляра.
Ответ №1:
Короткий ответ:
Вы можете переопределять методы, но переопределить поля невозможно.
Длинный ответ:
Каждый класс видит методы и поля своего собственного класса и своих родителей (за исключением частных методов). Если дочерний объект удаляет метод, имя которого совпадает с именем метода в его родительском классе, этот метод становится переопределенным — если этот метод каким-либо образом вызывается в дочернем экземпляре (даже из одного из родительских методов), вместо родительского будет использоваться совершенно новый метод. Дочерний элемент все еще может вызывать исходный метод своего последнего родителя через super.method(...)
call.
Но история меняется, когда мы переходим к полям. Если дочерний элемент объявляет новое поле, которое называется точно так же, как поле в родительском классе, оно просто скроет родительское поле без переопределения, точно так же, как локальная переменная скрывает глобальную. Таким образом, дочерние методы будут просто видеть дочерние поля, но родительский метод будет продолжать видеть родительское поле, а дочернее поле никоим образом не будет видно из родительского класса — вот что у вас есть.
Дочерний элемент может получить доступ к полю своего родителя через ((Parent)this).field
.
Комментарии:
1. Большинство людей имеют в виду статические методы / поля, когда говорят «методы класса / поля», но я почти уверен, что вы имеете в виду методы / поля экземпляра здесь.
Ответ №2:
Более длинный ответ:
Итак, на самом деле способ, которым вы бы это сделали, — определить Lion таким образом:
public class Lion extends Animal {
public Lion() {
noise = "ROAR!!";
}
}
Итак, теперь для экземпляров Lion переменная-член шума Animal была обновлена до ROAR!!
Конечно, у вас (почти) никогда не было бы общедоступного изменяемого члена в подобном классе в дикой природе.
Ответ №3:
Вы не можете переопределить поля, новое объявление noise
в Lion
скрывает noise
атрибут родителя. сделайте вот так:
public class Lion extends Animal {
// public String noise = "ROAR!!"; // <---- Remove this line
public Lion() {
noise = "ROAR";
}
public String toString() {
return noise;
}
}
Ответ №4:
Все нестатические методы в Java по умолчанию являются «виртуальными функциями». Если они не помечены как final (что делает метод недоступным переопределению). Java использует таблицу виртуальных методов для вызова метода правильного объекта.
Ответ №5:
Это потому, что Java говорит только о переопределении метода. Переменные-члены могут быть затенены только в дочернем классе. Итак, когда вы говорите c.noise, это фактически ссылается на строковую переменную в родительском классе как на c, если ссылочный тип Animal .
Ответ №6:
Темы, которые вас интересуют, — это динамическая отправка и таблица виртуальных методов. В принципе, по замыслу Java допускает переопределение методов (при условии, что они не являются окончательными), и во время выполнения JVM выполнит соответствующую реализацию. Этот полиморфный атрибут предоставляется только методам. Хороший дизайн OO диктует, что поля должны быть инкапсулированы в любом случае.