Рубрики
Без рубрики

Почему Переменная Экземпляра Суперкласса Не Переопределяется В Подклассе

Когда мы создаем переменную как в родительском, так и в дочернем классе с одинаковым именем и пытаемся получить к ней доступ с помощью… Помеченный java, полиморфизм.

Когда мы создаем переменную как в родительском, так и в дочернем классе с одинаковым именем и пытаемся получить к ней доступ, используя ссылку на родительский класс, в которой содержится объект дочернего класса, что мы получаем?

Чтобы понять это, давайте рассмотрим приведенный ниже пример, в котором мы объявляем переменную 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 , что и переменная экземпляра в Родительском классе, тогда

  1. Объект дочернего класса содержит обе переменные (одна унаследована от родительского класса, а другая объявлена в самом Дочернем ), но переменная дочернего класса скрывает переменную родительского класса.
  2. Потому что объявление x в классе Ребенок скрывает определение x в классе Родитель , в объявлении класса Ребенок , простое имя x всегда относится к полю , объявленному в классе Ребенок . И если код в методах Дочернего класса хочет ссылаться на переменную x родительского класса, то это можно сделать как super.x . Если мы пытаемся получить доступ к переменной за пределами

  3. родительского и Дочерний класс, затем переменная экземпляра выбирается из ссылочного типа. Таким образом, выражение родитель 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”