Когда мы создаем переменную как в родительском, так и в дочернем классе с одинаковым именем и пытаемся получить к ней доступ, используя ссылку на родительский класс, в которой содержится объект дочернего класса, что мы получаем?
Чтобы понять это, давайте рассмотрим приведенный ниже пример, в котором мы объявляем переменную x
с одинаковым именем в обоих родительских
и Ребенок
классы.
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 the child class with the 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)
будет печатать
Parent parent = new Child(); System.out.println(parent.x) // Output -- Parent`s Instance Variable
Ну, в общем, мы скажем, что Дочерний
класс переопределит переменную, объявленную в Родитель
класс и родитель.x
дадут нам все, что Ребенок
держит в руках предмет. Потому что это то же самое, что происходит, когда мы выполняем одни и те же операции с методами.
Но на самом деле это не так, и parent.x
даст нам значение переменной экземпляра родителя, которая объявлена в классе Parent
, но почему?
Поскольку переменные в Java не подчиняются полиморфизму, а переопределение применимо только к методам, но не к переменным. И когда переменная экземпляра в дочернем классе имеет то же имя, что и переменная экземпляра в родительском классе, тогда переменная экземпляра выбирается из ссылочного типа.
В Java, когда мы определяем переменную в дочернем классе с именем, которое мы уже использовали для определения переменной в родительском классе, переменная дочернего класса скрывает родительскую переменную, даже если их типы различны. И эта концепция известна как скрытие переменной .
Другими словами, когда дочерний и родительский классы имеют переменную с одинаковым именем, переменная дочернего класса скрывает переменную родительского класса. Вы можете прочитать больше о скрытии переменных в статье Что такое затенение и скрытие переменных в Java .
Скрытие переменной – это не то же самое, что переопределение метода
Хотя скрытие переменной выглядит как переопределение переменной, аналогичное переопределению метода, но это не так, переопределение применимо только к методам, в то время как скрытие применимо к переменным.
В случае переопределения метода переопределение методов полностью заменяет унаследованные методы, поэтому, когда мы пытаемся получить доступ к методу по родительской ссылке, удерживая дочерний объект, вызывается метод из дочернего класса. Вы можете прочитать больше о переопределении и о том, как переопределенные методы полностью заменяют унаследованные методы на Все о перегрузке методов Против Переопределения метода , Почему Мы Должны Следовать Правилам Переопределения Методов .
Но в скрытие переменной дочерний класс скрывает унаследованные переменные вместо замены, что в основном означает, что объект дочернего класса содержит обе переменные, но дочерняя переменная скрывает родительскую переменную. поэтому, когда мы попытаемся получить доступ к переменной из дочернего класса, доступ к ней будет получен из дочернего класса.
И если я упрощу раздел Пример 8.3.1.1-3. Скрытие переменных экземпляра из Спецификации языка Java :
Когда мы объявляем переменную в Дочернем
классе, которая имеет то же имя, например x
, что и переменная экземпляра в Родительском
классе, тогда
- Объект дочернего класса содержит обе переменные (одна унаследована от
родительского
класса, а другая объявлена в самомДочернем
), но переменная дочернего класса скрывает переменную родительского класса. Потому что объявление
x
в классеРебенок
скрывает определениеx
в классеРодитель
, в объявлении классаРебенок
, простое имяx
всегда относится к полю , объявленному в классеРебенок
. И если код в методахДочернего
класса хочет ссылаться на переменнуюx
родительскогокласса, то это можно сделать как
super.x.
Если мы пытаемся получить доступ к переменной за пределамиродительского
и
Дочернийкласс, затем переменная экземпляра выбирается из ссылочного типа. Таким образом, выражение
родитель 2.xв следующем коде присваивает значение переменной, принадлежащей родительскому классу, даже если в нем содержится объект
дочернегоно
((((((((Дочерний) родитель2).xобращается к значению из класса
Дочерний, потому что мы приводим ту же ссылку на
Дочерний.
Почему Скрытие Переменных Разработано Таким Образом
Таким образом, мы знаем, что переменные экземпляра выбираются из ссылочного типа, а не из типа экземпляра, и полиморфизм неприменим к переменным но на самом деле вопрос в том, почему? почему переменные предназначены для скрытия, а не для переопределения.
Потому что переопределение переменной может привести к нарушению методов, унаследованных от родителя, если мы изменим ее тип в дочернем классе.
Мы знаем, что каждый дочерний класс наследует переменные и методы (состояние и поведение) от своего родительского класса. Представьте, что Java допускает переопределение переменных, и мы меняем тип переменной с int
на Объект
в детском классе. Это приведет к поломке любого метода, использующего эту переменную, и поскольку дочерний элемент унаследовал эти методы от родительского, компилятор выдаст ошибки в классе дочерний элемент
.
Например:
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. }
Если Дочерний.x
переопределяет Родительский.x
, как можно увеличить()
и получить()
работу? В подклассе эти методы будут пытаться вернуть значение поля неправильного типа!
И, как уже упоминалось, если Java допускает переопределение переменной, дочерняя переменная не может заменить родительскую переменную, и это нарушит принцип замещаемости Лискова (LSP).
Почему переменная Экземпляра Выбрана из Ссылочного Типа Вместо Экземпляра
Как объяснено в Как JVM обрабатывает перегрузку и переопределение методов внутри , во время компиляции переопределяющие вызовы методов обрабатываются только из ссылочного класса, но все переопределенные методы заменяются переопределяющим методом во время выполнения с использованием виртуальной таблицы, и это явление называется полиморфизмом во время выполнения.
Аналогично, во время компиляции доступ к переменным также обрабатывается из ссылочного типа, но, как мы обсуждали, переменные не подчиняются переопределению или полиморфизму во время выполнения, поэтому они не заменяются переменными дочернего класса во время выполнения и по-прежнему ссылаются на ссылочный тип.
Вообще говоря, никто никогда не будет рекомендовать скрывать поля, так как это затрудняет чтение кода и создает путаницу. Такого рода путаницы не будет, если мы всегда будем придерживаться Общих рекомендаций по созданию POJO и инкапсулировать наши поля, объявляя их закрытыми и предоставляя геттеры/сеттеры по мере необходимости, чтобы переменные не были видны за пределами этого класса, и дочерний класс не может получить к ним доступ.
Вы можете найти полный код в этом репозитории Github и, пожалуйста, не стесняйтесь оставлять свои ценные отзывы.
Оригинал: “https://dev.to/njnareshjoshi/why-instance-variable-of-super-class-is-not-overridden-in-sub-class-105c”