У меня есть понимание скрытие переменных и переопределение методов и вызов виртуального метода в Java. Мой вопрос: почему скрытие переменных не действует в унаследованных методах? Означает ли это, что мы должны переопределить методы, которые обращаются к этим переменным в каждом дочернем классе?
Абстрактный класс
public abstract class ClassA{
protected int i = 0;
public void printOurStuff(){
System.out.println(i);
}
public void printMyStuff(){
System.out.println(this.i);
}
public void printSomeStuff(String s){
System.out.println(s);
}
}
Класс бетона
public class ClassB extends ClassA{
protected int i = 1;
public static main(String[] args){
ClassB b = new ClassB();
b.printOurStuff();
b.printMyStuff();
b.printSomeStuff(b.i);
}
}
Результаты
0 0 1
РЕДАКТИРОВАТЬ - изменил модификатор доступа к полю с private на protected и добавил метод printOurStuff
Нет. Это означает, что вам следует избегать скрытия полей. Если i
должен быть доступен в подклассах, сделайте его защищенным или добавьте защищенный метод получения, который вы можете вызывать в подклассах.
@JBNizet изменение модификатора доступа в любом случае не исправляет это. Однако я прошу объяснить, почему это происходит, а не как это сделать.
Это произойдет, если вы перестанете скрывать поля, то есть если вы удалите поле i
в ClassB и, таким образом, получите одно поле i
.
Обновлен вопрос, чтобы отразить интересующую меня проблему.
Когда вы объявляете частные поля, такие как
private int i = 0;
это означает, что только этот конкретный класс может получить доступ к этой переменной. Это поле недоступно для подклассов. Если вы хотите, чтобы это поле было доступно для подклассов, вы должны вместо этого сделать его protected
:
protected int i = 0;
Чтобы переопределить значение этого поля, вы можете использовать статический блок, например:
public class ClassB extends ClassA {
{
i = 1;
}
}
или присвойте новое значение в конструкторе:
public class ClassB extends ClassA {
public ClassB() {
i = 1;
}
}
Что касается вашего примера, если вы проверите объект ClassB
с помощью отладчика, вы обнаружите, что на самом деле у вас есть два поля i
: одно для ClassA
и одно для ClassB
.
Обновлено:
Что касается случая, когда переменная i
равна protected
:
Посмотрите внимательно на определение классов.
Вы не можете не согласиться, что вы объявитьi
поле два раза: для ClassA
и ClassB
. JVM будет уважать это заявление и следовать вашим инструкциям. Если поле protected
или даже public
, у вас все еще есть два поля. Вы не можете просто переопределить их, поскольку вы переопределяете методы. И при доступе к такому полю, как i = ...
, вы фактически получаете доступ к ближайшему полю к вашей области. Для ClassB
это его поле i
, а не поле его суперкласса ClassA
.
Кроме того, вы по-прежнему можете получить доступ к полю суперкласса следующим образом:
super.i = ...
super
— ссылка на суперкласс.
Установка модификатора доступа на частный была ошибкой, которую я сделал при публикации вопроса. Хотя добавление статического блока и присвоение переменной в конструкторе вполне приемлемо в качестве решения этой проблемы, меня больше беспокоит то, почему это происходит. Это из-за того, что объем конкретных методов в абстрактном классе ограничен абстрактным классом?
@KusalHettiarachchi нет. Это потому, что поля не полиморфны. Их нельзя переопределить. Только методы могут быть переопределены. Итак, если переменная (здесь this
) имеет тип ClassA, и вы обращаетесь к полю i
этой переменной, то компилятор статически разрешает ее в ClassA.i
.
@KusalHettiarachchi предоставил некоторые пояснения в ответе.
@JBNizet У меня нет проблем с тем, что b.i
разрешается до значения 1. Я хочу знать, почему метод printOurStuff
оценивает значение i до значения, установленного в ClassA
@PavelSmirnovyour, мой вопрос заключается в методе printOutStuff
оценке i как 0, хотя он вызывается для экземпляра ClassB.
И я ответил на это: потому что внутри printOurStuff неявная переменная this
имеет тип ClassA
, и, таким образом, i
статически разрешается в поле i
, объявленное в ClassA
. Поля не полиморфны. Они разрешаются во время компиляции.
@KusalHettiarachchi, printOutStuff
объявлен внутри ClassA
. Он даже не знает о ClassB
. Ссылка i
в поле System.out.println(i);
ссылки ClassA.i
. Это то же самое, что и System.out.println(this.i);
, где this
— ссылка на ClassA
.
@KusalHettiarachchi, просто подумайте об этом так: вы не переопределяете поле i
в ClassB
, вы не переопределяете printOutStuff
метод. Так почему же он должен использовать абсолютно новую переменную ClassB.i
вместо ClassA.i
?
@PavelSmirnov, значит, скрытие переменных не работает для конкретных методов, реализованных в абстрактных методах, если только мы не переопределим методы в дочерних классах?
Тогда именно поэтому необходимо инициализировать переменную в конструкторе или в статическом блоке. Спасибо @PavelSmirnov за руководство
@KusalHettiarachchi, на самом деле вам не нужно инициализировать его в конструкторе или в статическом блоке, если у вас нет конфликта имен. Это просто проблема области: две переменные с одинаковыми именами, поэтому компилятор выбирает наиболее близкую к области видимости.
Непонятно, какой результат вы хотите изменить - результат звонка
printMyStuff
или результат звонкаprintSomeStuff(b.i)
. Но это две совершенно разные переменные, и каждая переменная доступна для кода только в рамках одного и того же объявления класса, поскольку они обе являются закрытыми переменными.