Почему эта загадка абсолютная? По двум причинам:
• Она касается основ языка Java, а не какого-то малоизвестного нюанса API.
• Она расплавила мой мозг, когда я на нее наткнулся.
Если вы хотите проверить себя перед дальнейшим чтением, пройдите этот тест.
Для начала подготовим окружение. У нас будет 3 класса в 2 пакетах. Классы
Теперь давайте изменим видимость
Почему это происходит? Класс
Теперь давайте попробуем нечто иное: изменим модификатор
По-видимому, считается, что
Теперь давайте взглянем на еще один пример.
Лично я, когда впервые столкнулся с этой ситуацией, очень запутался и не мог разобраться, пока не изучил все примеры. Пока что можно сделать вывод, что подкласс является частью цепи наследования, тогда и только тогда, когда вы можете из него обратиться к
Это предположение тоже неверно. Рассмотрим следующий пример:
Мораль истории такова: хотя данное поведение и описано в спецификации, оно неинтуитивно. Кроме того, может существовать не одна цепь наследования, а много, и, меняя модификатор видимости с «по-умолчанию» на
UPD: используйте аннотацию Override, тогда в подобной ситуации код не скомпилируется.
• Она касается основ языка Java, а не какого-то малоизвестного нюанса API.
• Она расплавила мой мозг, когда я на нее наткнулся.
Если вы хотите проверить себя перед дальнейшим чтением, пройдите этот тест.
Для начала подготовим окружение. У нас будет 3 класса в 2 пакетах. Классы
C1 и C2 будут в пакете p1:package p1;
public class C1 {
public int m() {return 1;}
}
public class C2 extends C1 {
public int m() {return 2;}
}
* This source code was highlighted with Source Code Highlighter.Класс C3 будет в отдельном пакете p2:package p2;
public class C3 extends p1.C2 {
public int m() {return 3;}
}
* This source code was highlighted with Source Code Highlighter.Еще нам понадобится тестовый класс p1.Main с таким методом main:public static void main(String[] args) {
C1 c = new p2.C3();
System.out.println(c.m());
}
* This source code was highlighted with Source Code Highlighter.Обратите внимание, что мы вызываем метод класса С1 у экземпляра класса C3. Как можно было догадаться, этот пример выведет «3». Теперь давайте изменим видимость
m() во всех трех классах на видимость по умолчанию:public class C1 {
/*default*/ int m() {return 1;}
}
public class C2 extends C1 {
/*default*/ int m() {return 2;}
}
public class C3 extends p1.C2 {
/*default*/ int m() {return 3;}
}
* This source code was highlighted with Source Code Highlighter.Теперь выводом будет «2»!Почему это происходит? Класс
Main, который осуществляет вызов, не видит метод m() класса C3, поскольку тот находится в отдельном пакете. В том что касается Main, цепь наследования заканчивается на C2. Но так как C2 находится в том же пакете, то его метод m() переопределяет соответствующий метод C1. Не очень интуитивно, но так уж оно работает.Теперь давайте попробуем нечто иное: изменим модификатор
C3.m() обратно на public. Что получится теперь?public class C1 {
/*default*/ int m() {return 1;}
}
public class C2 extends C1 {
/*default*/ int m() {return 2;}
}
public class C3 extends p1.C2 {
public int m() {return 3;}
}
* This source code was highlighted with Source Code Highlighter.Теперь Main видит метод C3.m(). Но как это ни странно, результат, по-прежнему, «2»! По-видимому, считается, что
C3.m() вовсе не переопределяет C2.m(). Можно представлять себе это следующим образом: переопределяющий метод должен иметь доступ к методу, который он переопределяет (через super.m()). Однако в данном случае, у C3.m() нет доступа к своему базовому методу, поскольку тот находится в другом пакете. Поэтому C3 считается частью совершенно иной цепочки наследования, не той, что содержит C1 и C2. Если бы мы вызвали C3.m() непосредственно из Main, то получили бы ожидаемый результат «3».Теперь давайте взглянем на еще один пример.
protected — интересный модификатор видимости. Он ведет себя как модификатор по умолчанию для членов того же пакета и как public для подклассов. Что произойдет, если мы изменим все видимости на protected?public class C1 {
protected int m() {return 1;}
}
public class C2 extends C1 {
protected int m() {return 2;}
}
public class C3 extends p1.C2 {
protected int m() {return 3;}
}
* This source code was highlighted with Source Code Highlighter.Я рассуждал следующим образом: так как Main не является подклассом любого из наших классов, то protected, в данном случае, должен вести себя так же, как модификатор по умолчанию и результатом должно быть «2». Однако это не так. Важным моментом является то, что C3.m() имеет доступ к super.m() и, таким образом, на самом деле выводом будет «3».Лично я, когда впервые столкнулся с этой ситуацией, очень запутался и не мог разобраться, пока не изучил все примеры. Пока что можно сделать вывод, что подкласс является частью цепи наследования, тогда и только тогда, когда вы можете из него обратиться к
super.m(). Это предположение тоже неверно. Рассмотрим следующий пример:
public class C1 {
/*default*/ int m() {return 1;}
}
public class C2 extends C1 {
/*default*/ int m() {return 2;}
}
public class C3 extends p1.C2 {
/*default*/ int m() {return 3;}
}
public class C4 extends p2.C3 {
/*default*/ int m() {return 4;}
}
* This source code was highlighted with Source Code Highlighter.Заметим, что C4 находится в пакете p1. Теперь изменим код класса Main следующим образом:public static void main(String[] args) {
C1 c = new C4();
System.out.println(c.m());
}
* This source code was highlighted with Source Code Highlighter.Тогда он выведет «4». Однако super.m() не доступен из C4: если к C4.m() добавить аннотацию @Override, то код не будет компилироваться. В то же время, если мы изменим метод main наpublic static void main(String[] args) {
p2.C3 c = new C4();
System.out.println(c.m());
}
* This source code was highlighted with Source Code Highlighter.то результатом снова будет «3». Это означает, что C4.m() переопределяет C2.m() и C1.m(), но не C3.m(). Это также делает ситуацию еще более запутанной, а правильное предположение следующим: метод по��класса переопределяет метод базового класса, тогда и только тогда, когда метод в базовом классе доступен из подкласса. Здесь «базовый класс» относится к любому предку, не обязательно прямому родителю.Мораль истории такова: хотя данное поведение и описано в спецификации, оно неинтуитивно. Кроме того, может существовать не одна цепь наследования, а много, и, меняя модификатор видимости с «по-умолчанию» на
protected, вы можете поломать код совершенно в другом месте, даже не подозревая об этом, из-за того, что несколько цепей наследования объединятся в одну.UPD: используйте аннотацию Override, тогда в подобной ситуации код не скомпилируется.