Программирование на Java
Шрифт:
Итак, наследники могут объявлять поля с именами, совпадающими с родительскими полями. Такие объявления называют скрывающими. При этом объекты будут содержать оба значения, а компилятор будет каждый раз определять, с каким из них надо работать.
Методы
Рассмотрим случай переопределения (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;
Обращение к методу осуществляется
Что касается 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
То есть статические методы, подобно статическим полям, принадлежат классу и появление наследников на них не сказывается.
Статические методы не могут перекрывать обычные, и наоборот.
Полиморфизм и объекты
В заключение рассмотрим несколько особенностей, вытекающих из свойств полиморфизма.
Во-первых, теперь можно точно сформулировать, что является элементами ссылочного типа. Ссылочный тип обладает следующими элементами:
* непосредственно объявленными в его теле;
* объявленными в его родительском классе и реализуемых интерфейсах, кроме:
– private -элементов;
– "скрытых" элементов (полей и статических методов, скрытых одноименными элементами);
– переопределенных (динамических) методов.
Во-вторых, продолжим рассматривать взаимосвязь типа переменной и типов ее возможных значений. К случаям, описанным в предыдущей лекции, добавляются еще два. Переменная типа абстрактный класс может ссылаться на объекты, порожденные неабстрактным наследником этого класса. Переменная типа интерфейс может ссылаться на объекты, порожденные от класса, реализующего данный интерфейс.