Скрытие переменных экземпляра класса

#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 в производном классе; как вы сами обнаружили, обе переменные могут быть доступны независимо друг от друга.

Наилучший путь вперед действительно зависит от того, чего вы пытаетесь достичь:

  1. Если Parent.var не должно быть видно Child , сделайте это private .
  2. Если Parent.var и Child.var являются двумя логически различными переменными, дайте им разные имена, чтобы избежать путаницы.
  3. Если 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 вы вызываете переменную из родительского класса, независимо от того, имеют ли они одинаковые имена.

В качестве примечания я бы сказал, что это действительно сбивает с толку и не должно допускаться в качестве допустимого синтаксиса.