Программирование на Java
Шрифт:
Поэтому рассматриваемый пример эквивалентен:
System.out.println(Child.a)
System.out.println(Parent.a)
А его результат сомнений уже не вызывает:
3
2
Можно привести следующее пояснение. Статическое поле принадлежит классу, а не объекту. В результате появление классов-наследников со скрывающими (hiding) объявлениями никак не сказывается на работе с исходным полем. Компилятор всегда может определить, через ссылку какого типа происходит обращение к нему.
Обратите внимание на следующий пример:
class Parent {
static int a;
}
class Child extends Parent {
}
Каков
Child.a=10;
Parent.a=5;
System.out.println(Child.a);
В этом примере поле a не было скрыто и передалось по наследству классу Child. Однако результат показывает, что это все же одно поле:
5
Несмотря на то, что к полю класса идут обращения через разные классы, переменная всего одна.
Итак, наследники могут объявлять поля с именами, совпадающими с родительскими полями. Такие объявления называют скрывающими. При этом объекты будут содержать оба значения, а компилятор будет каждый раз определять, с каким из них надо работать.
Методы
Рассмотрим случай переопределения (overriding) методов:
class Parent {
public int getValue {
return 0;
}
}
class Child extends Parent {
public int getValue {
return 1;
}
}
И строки, демонстрирующие работу с этими методами:
Child c = new Child;
System.out.println(c.getValue);
Parent p = c;
System.out.println(p.getValue);
Результатом будет:
1
1
Можно видеть, что родительский метод полностью перекрыт, значение 0 никак нельзя получить через ссылку, указывающую на объект класса Child. В этом ключевая особенность полиморфизма – наследники могут изменять родительское поведение, даже если обращение к ним производится по ссылке родительского типа. Напомним, что, хотя старый метод снаружи уже недоступен, внутри класса-наследника к нему все же можно обратиться с помощью super.
Рассмотрим более сложный пример:
class Parent {
public int getValue {
return 0;
}
public void print {
System.out.println(getValue);
}
}
class Child extends Parent {
public int getValue {
return 1;
}
}
Что появится на консоли после выполнения следующих строк?
Parent p = new Child;
p.print;
С помощью ссылки типа Parent вызывается метод print, объявленный в классе Parent. Из этого метода делается обращение к getValue, которое в классе Parent возвращает 0. Но компилятор уже не может предсказать, к динамическому методу какого класса произойдет обращение во время работы программы. Это определяет виртуальная машина на основе объекта, на который указывает ссылка. И раз этот объект порожден от Child, то существует лишь один метод getValue.
Результатом работы примера будет:
1
Данный пример демонстрирует, что переопределение методов должно производиться с осторожностью. Если слишком сильно изменить логику их работы, нарушить принятые соглашения (например, начать возвращать null в качестве значения ссылочного типа, если родительский метод такого не допускал), это может привести к сбоям в работе родительского
класса, а значит, объекта наследника. Более того, существуют и некоторые обязательные ограничения.Вспомним, что заголовок метода состоит из модификаторов, возвращаемого значения, сигнатуры и throws -выражения. Сигнатура (имя и набор аргументов) остается неизменной, если говорить о переопределении. Возвращаемое значение также не может меняться, иначе это приведет к появлению двух разных методов с одинаковыми сигнатурами.
Рассмотрим модификаторы доступа.
class Parent {
protected int getValue {
return 0;
}
}
class Child extends Parent {
/* ??? */ protected int getValue {
return 1;
}
}
Пусть родительский метод был объявлен как protected. Понятно, что метод наследника можно оставить с таким же уровнем доступа, но можно ли его расширить (public), или сузить (доступ по умолчанию)? Несколько строк для проверки:
Parent p = new Child;
p.getValue;
Обращение к методу осуществляется с помощью ссылки типа Parent. Именно компилятор выполняет проверку уровня доступа, и он будет ориентироваться на родительский класс. Но ссылка-то указывает на объект, порожденный от Child, и по правилам полиморфизма исполняться будет метод именно этого класса. А значит, доступ к переопределенному методу не может быть более ограниченным, чем к исходному. Итак, методы с доступом по умолчанию можно переопределять с таким же доступом, либо protected или public. Protected -методы переопределяются такими же, или public, а для public менять модификатор доступа и вовсе нельзя.
Что касается private -методов, то они определены только внутри класса, снаружи не видны, а потому наследники могут без ограничений объявлять методы с такими же сигнатурами и произвольными возвращаемыми значениями, модификаторами доступа и т.д.
Аналогичные ограничения накладываются и на throws -выражение, которое будет рассмотрено в следующих лекциях.
Если абстрактный метод переопределяется неабстрактным, то говорят, что он его реализовал (implements). Как ни странно, абстрактный метод может переопределить другой абстрактный, или даже неабстрактный, метод. В первом случае такое действие может иметь смысл только при изменении модификатора доступа (расширении), либо throws -выражения. Во втором случае полностью утрачивается старая реализация метода, что может потребоваться в особенных случаях.
Перейдем к статическим методам. Рассмотрим пример:
class Parent {
static public int getValue {
return 0;
}
}
class Child extends Parent {
static public int getValue {
return 1;
}
}
И строки, демонстрирующие работу с этими методами:
Child c = new Child;
System.out.println(c.getValue);
Parent p = c;
System.out.println(p.getValue);
Аналогично случаю со статическими переменными, вспоминаем алгоритм обработки компилятором таких обращений к статическим элементам и получаем, что код эквивалентен следующим строкам:
System.out.println(Child.getValue);
System.out.println(Parent.getValue);
Результатом будет:
1
0
То есть статические методы, подобно статическим полям, принадлежат классу и появление наследников на них не сказывается.
Статические методы не могут перекрывать обычные, и наоборот.
Полиморфизм и объекты
В заключение рассмотрим несколько особенностей, вытекающих из свойств полиморфизма.