Когда мы создаем переменную в родительском и дочернем классах с одним и тем же именем и пытаемся получить к ней доступ, используя ссылку на родительский класс, который содержит объект дочернего класса, тогда что мы получим?
Чтобы понять это, рассмотрим пример ниже, где мы объявляем переменную x
с одинаковым именем в Parent
и Child
классах.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class Parent { // Declaring instance variable by name `x` String x = "Parent`s Instance Variable" ; public void print() { System.out.println(x); } } class Child extends Parent { // Hiding Parent class's variable `x` by defining a variable in child class with same name. String x = "Child`s Instance Variable" ; @Override public void print() { System.out.print(x); // If we still want to access variable from super class, we do that by using `super.x` System.out.print( ", " + super .x + "\n" ); } } |
А теперь, если мы попытаемся получить доступ к x
с помощью приведенного ниже кода, то что System.out.println(parent.x)
будет печатать
1
2
|
Parent parent = new Child(); System.out.println(parent.x) // Output -- Parent`s Instance Variable |
В общем, мы скажем, что класс Child
переопределит переменную, объявленную в классе Parent
а parent.x
даст нам то, Child's
содержит объект Child's
. Потому что это то же самое, что происходит, когда мы выполняем те же операции над методами.
Но на самом деле это не так, и parent.x
даст нам значение Parent`s Instance Variable, которое объявлено в классе Parent
но почему?
Поскольку переменные в Java не следуют полиморфизму, а переопределение применимо только к методам, но не к переменным. И когда переменная экземпляра в дочернем классе имеет то же имя, что и переменная экземпляра в родительском классе, тогда переменная экземпляра выбирается из ссылочного типа.
В Java, когда мы определяем переменную в дочернем классе с именем, которое мы уже использовали для определения переменной в родительском классе, переменная дочернего класса скрывает родительскую переменную, даже если их типы различны. И эта концепция известна как скрытие переменных.
Другими словами, когда дочерний и родительский класс имеют переменную с одинаковым именем, переменная дочернего класса скрывает переменную родительского класса. Вы можете прочитать больше о скрытии переменных в статье Что такое скрытие и скрытие переменных в Java .
Скрытие переменных — это не то же самое, что переопределение метода
Хотя скрытие переменных выглядит как переопределение переменной, аналогично переопределению метода, но это не так, переопределение применимо только к методам, в то время как скрытие применимо к переменным.
В случае переопределения методов переопределенные методы полностью заменяют унаследованные методы, поэтому, когда мы пытаемся получить доступ к методу из родительской ссылки, удерживая дочерний объект, вызывается метод из дочернего класса. Вы можете прочитать больше о переопределении и о том, как переопределенные методы полностью заменяют унаследованные методы, в разделе Все о перегрузке метода и переопределении метода , почему мы должны следовать правилам переопределения метода.
Но в скрытии переменных дочерний класс скрывает унаследованные переменные вместо замены, что в основном означает, что объект класса Child содержит обе переменные, а переменная Child скрывает переменную Parent. поэтому, когда мы пытаемся получить доступ к переменной из дочернего класса, она будет доступна из дочернего класса.
И если я упросту раздел Пример 8.3.1.1-3. Скрытие переменных экземпляра спецификации языка Java :
Когда мы объявляем переменную в Child
классе с таким же именем, например, x
как переменная экземпляра в Parent
классе, тогда
- Объект дочернего класса содержит обе переменные (одна унаследована от
Parent
класса, а другая объявлена в самом дочернем классе), но переменная дочернего класса скрывает переменную родительского класса. - Поскольку объявление
x
в классеChild
скрывает определениеx
в классеParent
, в объявлении классаChild
простое имяx
всегда ссылается на поле, объявленное в классеChild
. И если код в методах классаChild
хочет ссылаться на переменнуюx
классаParent
то это можно сделать какsuper.x
. - Если мы пытаемся получить доступ к переменной вне класса
Parent
иChild
, тогда переменная экземпляра выбирается из ссылочного типа. Таким образом, выражениеparent2.x
в следующем коде дает значение переменной, которая принадлежит родительскому классу, даже если она содержит объектChild
но((Child) parent2).x
обращается к значению из классаChild
потому что мы произвели то же самое ссылка наChild
.
Почему переменная скрывается так
Итак, мы знаем, что переменные экземпляра выбираются из ссылочного типа, а не типа экземпляра, и полиморфизм не применим к переменным, но реальный вопрос — почему? почему переменные предназначены для сокрытия, а не переопределения.
Потому что переопределение переменной может нарушить методы, унаследованные от родительского, если мы изменим ее тип в дочернем классе.
Мы знаем, что каждый дочерний класс наследует переменные и методы (состояние и поведение) от своего родительского класса. Представьте себе, если Java допускает переопределение переменных, и мы меняем тип переменной с int
на Object
в дочернем классе. Это сломает любой метод, использующий эту переменную, и поскольку дочерний объект унаследовал эти методы от родительского, компилятор выдаст ошибки в child
классе.
Например:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
class Parent { int x; public int increment() { return ++x; } public int getX() { return x; } } class Child extends Parent { Object x; // Child is inherting increment(), getX() from Parent and both methods returns an int // But in child class type of x is Object, so increment(), getX() will fail to compile. } |
Если Child.x
переопределяет Parent.x
, как могут работать increment()
и getX()
? В подклассе эти методы будут пытаться вернуть значение поля неправильного типа!
И, как уже упоминалось, если Java допускает переопределение переменных, тогда переменная Child не может заменить переменную Parent, и это нарушит принцип заменяемости Лискова (LSP).
Почему переменная экземпляра выбирается из ссылочного типа вместо экземпляра
Как объясняется в разделе Как внутренняя перегрузка и переопределение JVM обрабатывает методы, во время компиляции вызовы переопределенных методов обрабатываются только из ссылочного класса, но все переопределенные методы заменяются переопределяющим методом во время выполнения с использованием vtable, и это явление называется полиморфизмом времени выполнения.
Точно так же, во время компиляции доступ к переменным также обрабатывается из ссылочного типа, но, как мы обсуждали, переменные не следуют переопределению или полиморфизму времени выполнения, поэтому они не заменяются переменными дочернего класса во время выполнения и все еще ссылаются на ссылочный тип.
Вообще говоря, никто никогда не будет рекомендовать скрывать поля, так как это затрудняет чтение кода и создает путаницу. Такой путаницы не будет, если мы всегда будем придерживаться
Общие рекомендации по созданию POJO и инкапсуляции наших полей, объявляя их частными и предоставляя средства получения / установки по мере необходимости, так что переменные не видны вне этого класса, и дочерний класс не может получить к ним доступ.
Вы можете найти полный код в этом репозитории Github и, пожалуйста, не стесняйтесь, оставляйте ценные отзывы.
Опубликовано на Java Code Geeks с разрешения Нареша Джоши, партнера нашей программы JCG . См. Оригинальную статью здесь: Почему переменная экземпляра суперкласса не переопределяется в подклассе
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |