#java #oop
#java #ооп
Вопрос:
Мне интересно, почему Java имеет такое странное поведение в отношении суперкласса и подкласса, имеющих переменные экземпляра с одинаковым именем.
Допустим, у нас есть следующие определения классов:
class Parent {
int var = 1;
}
class Child extends Parent {
int var = 2;
}
Делая это, мы должны были скрыть переменную суперкласса var
. И если мы явно не указываем способ доступа Parent
к ‘s var
через super
вызов, то мы никогда не сможем получить доступ var
из экземпляра дочернего элемента.
Но когда у нас есть приведение, этот механизм скрытия ломается:
Child child = new Child();
Parent parent = (Parent)child;
System.out.println(parent.var); // prints out 1, instead of 2
Разве это не полностью обходит весь смысл скрытия полей? Если это так, то разве это не делает идею совершенно бесполезной?
РЕДАКТИРОВАТЬ: я имею в виду конкретно эту статью в учебниках по Java. В нем упоминается
Внутри подкласса на поле в суперклассе нельзя ссылаться по его простому имени. Вместо этого доступ к полю должен осуществляться через super …
Из того, что я там прочитал, похоже, подразумевается, что разработчики Java имели в виду какую-то технику для этого. Хотя я согласен, что это довольно неясная концепция и, вероятно, плохая практика в целом.
Комментарии:
1. Мне действительно интересно, почему компилятор не запрещает это. Я не могу придумать случай, когда это было бы полезно.
2. Если вы хотите, чтобы значение var отличалось для дочернего элемента от родительского?
Ответ №1:
В Java элементы данных не являются полиморфными.Это означает, что Parent.var
и Child.var
— это две разные переменные, которые имеют одно и то же имя. Вы ни в каком смысле не «переопределяете» var
в производном классе; как вы сами обнаружили, обе переменные могут быть доступны независимо друг от друга.
Наилучший путь вперед действительно зависит от того, чего вы пытаетесь достичь:
- Если
Parent.var
не должно быть видноChild
, сделайте этоprivate
. - Если
Parent.var
иChild.var
являются двумя логически различными переменными, дайте им разные имена, чтобы избежать путаницы. - Если
Parent.var
иChild.var
логически являются одной и той же переменной, то используйте для них один элемент данных.
Ответ №2:
«Смысл» скрытия полей заключается просто в том, чтобы указать поведение кода, который присваивает переменной то же имя, что и переменной в ее суперклассе.
Это не предназначено для использования в качестве метода для реального сокрытия информации. Это делается путем создания переменных закрытыми для начала… Я бы настоятельно рекомендовал использовать частные переменные практически во всех случаях. Поля — это деталь реализации, которая должна быть скрыта от всего остального кода.
Комментарии:
1. @tskuzzy: Я думаю, что это просто говорит о коде внутри класса, на самом деле. Это, конечно, не попытка превратить скрытие полей во что-то, что вы делаете с целью удаления доступа к полю.
Ответ №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 переменные не являются полиморфными, а скрытие переменных — это не то же самое, что переопределение метода
Хотя скрытие переменной выглядит как переопределение переменной, аналогичное переопределению метода, но это не так, переопределение применимо только к методам, в то время как скрытие применимо к переменным.
В случае переопределения метода переопределенные методы полностью заменяют унаследованные методы, поэтому, когда мы пытаемся получить доступ к методу из родительской ссылки, удерживая дочерний объект, вызывается метод из дочернего класса.
Но в скрытии переменных дочерний класс скрывает унаследованные переменные вместо замены, поэтому, когда мы пытаемся получить доступ к переменной из родительской ссылки, удерживая дочерний объект, доступ к нему будет осуществляться из родительского класса.
public static void main(String[] args) throws Exception {
Parent parent = new Parent();
parent.printInstanceVariable(); // Output - "Parent`s Instance Variable"
System.out.println(parent.x); // Output - "Parent`s Instance Variable"
Child child = new Child();
child.printInstanceVariable();// Output - "Child`s Instance Variable, Parent`s Instance Variable"
System.out.println(child.x);// Output - "Child`s Instance Variable"
parent = child; // Or parent = new Child();
parent.printInstanceVariable();// Output - "Child`s Instance Variable, Parent`s Instance Variable"
System.out.println(parent.x);// Output - Parent`s Instance Variable
// Accessing child's variable from parent's reference by type casting
System.out.println(((Child) parent).x);// Output - "Child`s Instance Variable"
}
Как мы можем видеть выше, когда переменная экземпляра в подклассе имеет то же имя, что и переменная экземпляра в суперклассе, тогда переменная экземпляра выбирается из ссылочного типа.
Объявление переменных с одинаковым именем как в дочернем, так и в родительском элементе создает путаницу, мы всегда должны избегать этого, чтобы не было путаницы. И именно поэтому мы также должны всегда придерживаться общих рекомендаций по созданию POJO и объявлению наших переменных с частным доступом, а также предоставлять надлежащие методы get / set для доступа к ним.
Вы можете прочитать больше в моей статье, что такое затенение и скрытие переменных в Java.
Ответ №5:
Когда вы выполняете кастинг, вы фактически сообщаете компилятору «Я знаю лучше» — это приостанавливает действие обычных правил вывода с строгой типизацией и дает вам преимущество сомнения.
Говоря, что Parent parent = (Parent)child;
вы сообщаете компилятору «обрабатывайте этот объект так, как если бы он был экземпляром Parent».
С другой стороны, вы путаете принцип «сокрытия информации» OO (хорошо!) с побочным эффектом сокрытия полей (обычно плохим).
Ответ №6:
Как вы указали:
предполагается, что мы скрыли переменную суперкласса var
Главное здесь в том, что переменные не переопределяются, как это делают методы, поэтому при прямом вызове дочернего элемента.var вы вызываете переменную непосредственно из дочернего класса, а при вызове Parent.var вы вызываете переменную из родительского класса, независимо от того, имеют ли они одинаковые имена.
В качестве примечания я бы сказал, что это действительно сбивает с толку и не должно допускаться в качестве допустимого синтаксиса.