Пример в этом разделе демонстрирует основные концепции ООП, в последующих разделах главы концепции, представленные здесь, рассматриваются более подробно.
На рис.6-2 показано отношения наследование между классом String
и его суперклассом Object
. Клиент, который использует объект типа String
, определяется в примере 6.2. Во время выполнения метода main()
на созданной в нем строке (1) объект класса String указывают две ссылки: ссылка stringRef
подкласса String
и сслыкa objRef
суперкласса Object
. Изучая код метода main()
, познакомьтесь с особенностями ООП.
// String подкласс Object
class Client {
public static void main(String[] args) {
String stringRef = new String("Java"); // (1)
System.out.println("(2): " + stringRef.getClass()); // (2)
System.out.println("(3): " + stringRef.length()); // (3)
Object objRef = stringRef; // (4)
// System.out.println("(5): " + objRef.length()); // (5) не верно.
System.out.println("(6): " + objRef.equals("Java")); // (6)
System.out.println("(7): " + objRef.getClass()); // (7)
stringRef = (String) objRef; // (8)
System.out.println("(9): " + stringRef.equals("C++")); // (9)
}
}
Вывод программы:
(2): class java.lang.String
(3): 4
(6): true
(7): class java.lang.String
(9): false
Подкласс String
наследует метод getClass()
от суперкласса Object
. Клиент класса String
может напрямую вызывать этот унаследованный метод в объектах класса String
таким же образом, как если бы метод был определен в самом классе String
. В примере 6.2 это показано в строке (2).
System.out.println("(2): " + stringRef.getClass()); // (2)
Подкласс String
определяет метод length()
, которого нет в суперклассе Object
, таким способом расширяет суперкласс. В примере 6.2 вызов этого нового метода объекта показан в строке (3).
System.out.println("(3): " + stringRef.length()); // (3)
Ссылка на подкласс может быть присвоена ссылке на суперкласс, потому что объект подкласса может использоваться везде, где может использоваться объект суперкласса. Это называется восходящим преобразованием, так как ссылка присваивается на элемент выше в иерархии наследования. В примере 6.2 это показано в строке (4), в которой значение ссылки подкласса stringRef
присваивается ссылке суперкласса objRef
.
Object objRef = stringRef; // (4)
Обе ссылки указывают на один и тот же объект string после присваивания. Теперь может показаться привлекательным вызов методов подкласса String через ссылку суперкласса objRef, как показано в строке (5).
System.out.println("(5): " + objRef.length()); // (5) Неверно.
Однако это не будет работать, потому что компилятор не знает, на какой объект указывает ссылка objRef
. Он знает только тип ссылки. Так как в объявлении класса Object
нет метода с именем length()
, вызов метода length()
в строке (5) будет отмечен компилятором как ошибка.
В противоположность ситуации в строке (5) вызов метода equals()
в строке (6) через ссылку суперкласса objRef
допустим, потому что компилятор может проверить, что в классе Object
определен метод equals()
.
System.out.println("(6): " + objRef.equals("Java")); // (6)
Обратите внимание, что этот метод подменяется в классе String
на метод с такой же сигнатурой (т.е. имя метода и параметры) и таким же возвращаемым типом. Это называется Реопредепением метода.
Во время выполнения вызов метода equals()
в строке (6) через ссылку суперкласса objRef
не обязательно приведет к вызову метода equals()
из класса Object
. Вызываемый метод зависит от типа реального объекта, на который указывает ссылка, во время выполнения. Реальный метод находится при помощи механизма динамического поиска метода. Способность ссылки суперкласса обозначать объекты своего собственного класса и его подклассов на этапе выполнения называется полиморфизмом.
В случае нормального выполнения программы ссылка objRef
будет ссылаться на объект класса String
в строке (6), и в результате будет выполнен метод equals()
класса String
, а не одноименный метод из класса Object
.
Ситуация в строке (7), в которой метод getclass()
вызывается через ссылку суперкласса objRef
, допустима, потому что метод getClass
определяется в классе Object
.
System.out.println("(7): " + objRef.getClass()); // (7)
В этом случае при нормальном ходе выполнения программы ссылка objRef
будет ссылаться на объект класса String
в строке (7). Динамический поиск метода определит, какая реализация соответствует сигнатуре метода getClass()
. Поскольку ни один метод getClass()
не определен в классе String
, выполняется метод getClass()
, унаследованный от объекта Object
.
Преобразование типа значения ссылки суперкласса к типу подкласса называется нисходящим преобразованием. Это показано в примере 6.2 с помощью операции присваивания, которая требует явного приведения типа.
tringRef = (String) objRef; // (8)
System.out.println("(9): " + stringRef.equals("C++")); // (9)
В строке (8) исходная ссылка objRef
имеет тип Object
, который является суперклассом для класса ссылки назначения stringRef
. Если ссылка objRef
реально обозначала объект класса String
во время выполнения, то механизм приведения преобразует ее в правильный тип, так что присваивание ее ссылке stringRef
допустимо в строке (8). Ссылка stringRef
может потом использоваться для вызова метода equals()
объекта String
, как показано в строке (9). Не удивляйтесь, что будет выполнен метод equals()
из класса String
.
Компилятор проверяет, что существует отношение наследования между исходным типом ссылки и типом ссылки, определяемым в приведении. Однако преобразование может оказаться невозможным на этапе выполнения. Если во время выполнения ссылка objRef
обозначает объект класса Object
или какой-то не имеющий отношения к делу подкласс класса Object
, то, очевидно, преобразование значения этого типа к ссылке подкласса String
недопустимо. В этом случае во время выполнения будет выброшено исключение ClassCastException
. Для выяснения типа объекта перед тем, как к нему будут применены какие-либо преобразования, может использоваться оператор instanceof
.
Одиночное наследование реализации | Переопределение и сокрытие методов |