Скрытие переменных экземпляра класса
Мне интересно, почему Java имеет такое странное поведение в отношении суперкласса и подкласса с переменными экземпляра с тем же именем.
Скажем, мы имеем следующие определения классов:
class Parent {
int var = 1;
}
class Child extends Parent {
int var = 2;
}
Таким образом, мы должны спрятать переменную суперкласса var
. И если мы явно не укажем способ доступа к Parent
var
с помощью вызова super
, мы никогда не сможем получить доступ к var
из экземпляра дочернего элемента.
Но когда у нас есть бросок, этот механизм скрытия ломается:
Child child = new Child();
Parent parent = (Parent)child;
System.out.println(parent.var); // prints out 1, instead of 2
Разве это не полностью обходит всю точку сокрытия? Если это так, то разве это не делает идею совершенно бесполезной?
РЕДАКТИРОВАТЬ. Я специально ссылаюсь на эту статью в учебниках по Java. Он упоминает
Внутри подкласса поле в суперклассе не может быть привязано кпо его простому имени. Вместо этого поле должно получить доступ через супер...
Из того, что я там читаю, кажется, подразумевается, что разработчики Java имели в виду какую-то технику. Хотя я согласен, что это довольно неясная концепция и, вероятно, будет плохой практикой в целом.
Ответы
Ответ 1
В Java члены данных не являются полиморфными. Это означает, что Parent.var
и Child.var
являются двумя разными переменными, которые имеют одно и то же имя. Вы ни в каком смысле не "переопределяете" var
в производном классе; как вы обнаружили, обе переменные могут быть доступны независимо друг от друга.
Лучший способ продвижения действительно зависит от того, чего вы пытаетесь достичь:
- Если
Parent.var
не должно быть видимым для Child
, сделайте его private
.
- Если
Parent.var
и Child.var
являются двумя логически отличными переменными, дайте им разные имена, чтобы избежать путаницы.
- Если
Parent.var
и Child.var
являются логически одной и той же переменной, тогда используйте для них один элемент данных.
Ответ 2
"Точка" скрытия поля - это просто указать поведение кода, который дает переменную с тем же именем, что и в ее суперклассе.
Он не предназначен для использования в качестве метода, чтобы искренне скрывать информацию. Это делается с учетом того, что частные переменные начинаются с... Я бы настоятельно рекомендовал использовать частные переменные практически во всех случаях. Поля - это деталь реализации, которая должна быть скрыта от всего другого кода.
Ответ 3
Атрибуты не являются полиморфными в Java, и в любом случае объявление публичного атрибута не всегда является хорошей идеей. Для поведения, которое вы ищете, лучше использовать частные атрибуты и методы доступа, например:
class Parent {
private int var = 1;
public int getVar() {
return var;
}
public void setVar(int var) {
this.var = var;
}
}
class Child extends Parent {
private int var = 2;
public int getVar() {
return var;
}
public void setVar(int var) {
this.var = var;
}
}
И теперь, при тестировании, мы получаем желаемый результат, 2:
Child child = new Child();
Parent parent = (Parent)child;
System.out.println(parent.getVar());
Ответ 4
Этот сценарий известен как спряжение переменных. Когда у дочернего и родительского классов есть переменная с тем же именем, переменная дочернего класса скрывает переменную родительского класса, и этот процесс называется спряжением переменной.
В Java переменные не являются полиморфными, а переменная Скрытие не такая же, как переопределение метода
Хотя спряжение переменной выглядит как переопределение переменной, аналогичной переопределению метода, но это не так, Overriding применим только к методам, а скрытие - применимые переменные.
В случае переопределения метода переопределенные методы полностью заменяют унаследованные методы, поэтому, когда мы пытаемся получить доступ к методу из родительской ссылки, удерживая дочерний объект, вызывается метод из дочернего класса.
Но в переменной hiding child класс скрывает унаследованные переменные вместо замены, поэтому, когда мы пытаемся получить доступ к переменной из родительской ссылки, удерживая дочерний объект, она будет доступна из родительского класса.
public static void main(String[] args) throws Exception {
Parent parent = new Parent();
parent.printInstanceVariable(); // Output - "Parent Instance Variable"
System.out.println(parent.x); // Output - "Parent Instance Variable"
Child child = new Child();
child.printInstanceVariable();// Output - "Child Instance Variable, Parent Instance Variable"
System.out.println(child.x);// Output - "Child Instance Variable"
parent = child; // Or parent = new Child();
parent.printInstanceVariable();// Output - "Child Instance Variable, Parent Instance Variable"
System.out.println(parent.x);// Output - Parent Instance Variable
// Accessing child variable from parent reference by type casting
System.out.println(((Child) parent).x);// Output - "Child Instance Variable"
}
Как мы видим выше, когда переменная экземпляра в подклассе имеет то же имя, что и переменная экземпляра в суперклассе, тогда переменная экземпляра выбирается из ссылочного типа.
Объявление переменных с одним и тем же именем в дочерних и родительских целях создает путаницу, мы всегда должны избегать этого, чтобы не было путаницы. И поэтому мы также должны всегда придерживаться общих руководящих принципов создания POJO и объявлять наши переменные с помощью частного доступа, а также предоставлять надлежащие методы get/set для их доступа.
Вы можете больше узнать о том, что такое переменная тени и скрытие в Java.
Ответ 5
Когда вы выполняете кастинг, вы фактически сообщаете компилятору "Я знаю лучше" - он приостанавливает обычные правила ввода текста и дает вам преимущество.
Говоря Parent parent = (Parent)child;
, вы сообщаете компилятору "относиться к этому объекту так, как если бы это был экземпляр родителя".
С другой стороны, вы вводите в заблуждение принцип "скрытия информации" OO (хороший!) с побочным эффектом, скрывающим поле (обычно это плохо).
Ответ 6
Как вы указали:
мы должны спрятать переменную суперкласса var
Главное здесь - переменные не переопределять, как это делают методы, поэтому, когда вы вызываете непосредственно Child.var, вы вызываете переменную непосредственно из класса Child и когда вы вызываете Parent.var, вы вызываете переменную из класса Parent, независимо от того, имеют ли они одно и то же имя.
В качестве побочной заметки я бы сказал, что это действительно запутанно и не должно быть разрешено как допустимый синтаксис.