Правила совместимости ссылочных типов как основа использования полиморфного кода. Приведение и проверка типов
Мы уже говорили, что полиморфный код обеспечивает основные преимущества объектного программирования. Но как им воспользоваться? Ведь тип объектных переменных задаётся на этапе компиляции. Решением проблемы является следующее правило:
переменной некоторого объектного типа можно присваивать выражение, имеющее тот же тип или тип класса-наследника.
Аналогичное правило действует при передаче фактического параметра в подпрограмму:
В качестве фактического параметра вместо формального параметра некоторого объектного типа можно подставлять выражение, имеющее тот же тип или тип класса-наследника.
В качестве выражения может выступать переменная объектного типа, оператор создания нового объекта (слово new, за которым следует конструктор), функция объектного типа (в том числе приведения объектного типа).
Поэтому если мы создадим переменную базового типа, для которой можно писать полиморфный код, этой переменной можно назначить ссылку на объект, имеющий тип любого из классов-потомков. В том числе – ещё не написанных на момент компиляции базового класса. Пусть, например, мы хотим написать подпрограмму, позволяющую перемещать фигуры из нашей иерархии не в точку с новыми координатами, как метод moveTo, а на необходимую величину dx и dy по соответствующим осям. При этом у нас отсутствуют исходные коды базового класса нашей иерархии (либо их запрещено менять). Для этих целей создадим класс FiguresUtil (сокращение от Utilities – утилиты, служебные программы), а в нём зададим метод moveFigureBy (“переместить фигуру на”).
public class FiguresUtil{
public static void moveFigureBy(Figure figure,int dx, int dy){
figure.moveTo(figure.x+dx, figure.y+dy);
}
}
В качестве фактического параметра такой подпрограммы вместо figure можно подставлять выражение, имеющее тип любого класса из иерархии фигур. Пусть, например, новая фигура создаётся по нажатию на кнопку в зависимости от того, какой выбор сделал пользователь во время работы программы: если в радиогруппе отмечен пункт “Точка”, создаётся объект типа Dot. Если в радиогруппе отмечен пункт “Окружность”, создаётся объект типа Circle. Если же отмечен пункт “Круг”, создаётся объект типа FilledCircle. Отметим также, что класс FilledCircle был написан уже после компиляции классов Figure, Dot и Circle.
Фрагмент кода для класса нашего приложения будет выглядеть так:
Figure figure;
java.awt.Graphics g=jPanel1.getGraphics();
//обработчик кнопки создания фигуры
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
if(jRadioButton1.isSelected() )
figure=new Dot(g,jPanel1.getBackground());
if(jRadioButton2.isSelected())
figure=new Circle(g,jPanel1.getBackground());
if(jRadioButton3.isSelected())
figure=new FilledCircle(g,jPanel1.getBackground(),20,
java.awt.Color.BLUE);
figure.show();
}
//обработчик кнопки передвижения фигуры
private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
int dx= Integer.parseInt(jTextField1.getText());
int dy= Integer.parseInt(jTextField2.getText());
FiguresUtil.moveFigureBy(figure,dx,dy);
}
При написании программы неизвестно, ни какого типа будет передвигаемый объект, ни насколько его передвинут – всё зависит от решения пользователя во время работы программы. Именно возможность назначения ссылки на объект класса-потомка обеспечивает возможность использования полиморфного кода.
Следует обратить внимание на ещё один момент – стиль написания вызова FiguresUtil.moveFigureBy(figure,dx,dy);
Можно было бы написать его так:
FiguresUtil.moveFigureBy(
figure,
Integer.parseInt(jTextField1.getText()),
Integer.parseInt(jTextField2.getText())
);
При этом экономились бы две локальные переменные (аж целых 8 байт памяти!), но читаемость, понимаемость и отлаживаемость кода стали бы гораздо меньше.
Часто встречающаяся ошибка: пытаются присвоить переменной типа “наследник” выражение типа “прародитель”. Например,
Figure figure;
Circle circle;
…
figure =new Circle (); //так можно
…
circle= figure; - Так нельзя! Выдастся ошибка компиляции. Несмотря на то, что переменной figure назначен объект типа Circle – ведь проверка на допустимость присваивания делается на этапе компиляции, а не динамически.
Если программист уверен, что объект имеет тип класса-потомка, в таких случаях надо использовать приведение типа. Для приведения типа перед выражением или именем переменной в круглых скобках ставят имя того типа, к которому надо осуществить приведение:
Figure figure;
Circle circle;
Dot dot;
…
figure =new Circle (); //так можно
…
circle= (Circle)figure; //так можно!
dot=(Dot) figure; //так тоже можно!
Отметим, что приведение типа принципиально отличается от преобразования типа, хотя синтаксически записывается так же. Преобразование типа приводит к изменению содержимого ячейки памяти и может приводить к изменению её размера. А вот приведение типа не меняет ни размера, ни содержимого никаких ячеек памяти – оно меняет только тип, сопоставляемый ячейке памяти. В Java приведение типа применяется к ссылочным типам, а преобразование – к примитивным. Это связано с тем, что изменение типа ссылочной переменной не приводит к изменению той ячейки, на которую она ссылается. То есть в случае приведения тип объекта не меняется – меняется тип ссылки на объект.
Приводить тип можно как в сторону генерализации, так и в сторону специализации.
Приведение в сторону генерализации является безопасным, так как объект класса-потомка всегда является экземпляром прародителя, хоть и усложнённым А вот приведение в сторону специализации является опасным – вполне допустимо, что во время выполнения программы окажется, что объект, назначенный переменной, не является экземпляром нужного класса. Например, при приведении (Circle)figure может оказаться, что переменной figure назначен объект типа Dot, который не может быть приведён к типу Circle. В этом случае возникает исключительная ситуация приведения типа (typecast).
Возможна программная проверка того, что объект является экземпляром заданного класса:
if(figure instanceof Circle)
System.out.println("figure instanceof Circle");
Иногда вместо работы с самими классами бывает удобно использовать ссылки на класс. Они получаются с помощью доступа к полю .class из любого класса.
Возможно создание переменных типа “ссылка на класс”:
Class c=Circle.class;
Их можно использовать для обращения к переменным класса и методам класса. Кроме того, переменных типа “ссылка на класс” можно использовать для создания экземпляров этого класса с помощью метода newInstance():
Circle circle=(Circle)c.newInstance();
Возможна программная проверка соответствия объекта нужному типу с помощью ссылки на класс:
if(figure.getClass()==Circle.class)
circle= (Circle)figure;
…;
Но следует учитывать, что при такой проверке идёт сравнение на точное равенство классов, а не на допустимость приведения типов. А вот оператор isInstance позволяет проверять, является ли объект figure экземпляром класса, на который ссылается c :
if(c.isInstance(figure))
System.out.println("figure isInstance of Circle");
- Содержание
- Глава 1. Общие представления о языке Java 6
- Глава 2. Объектно-ориентированное проектирование и платформа NetBeans 26
- Глава 3. Примитивные типы данных и операторы для работы с ними 78
- Глава 4. Работа с числами в языке Java 95
- Глава 5. Управляющие конструкции 112
- Глава 6. Начальные сведения об объектном программировании 128
- Глава 7. Важнейшие объектные типы 175
- Введение
- Глава 1. Общие представления о языке Java
- 1.1. Java и другие языки программирования. Системное и прикладное программирование
- 1.2. Виртуальная Java-машина, байт-код, jit-компиляция. Категории программ, написанных на языке Java
- 1.3.Алфавит языка Java. Десятичные и шестнадцатеричные цифры и целые числа. Зарезервированные слова Алфавит языка Java
- Десятичные и шестнадцатеричные цифры и целые числа
- Зарезервированные слова языка Java
- 1.4. Управляющие последовательности. Символы Unicode. Специальные символы Управляющие последовательности
- Простые специальные символы
- Составные специальные символы
- 1.5.Идентификаторы. Переменные и типы. Примитивные и ссылочные типы
- Краткие итоги по главе 1
- Задания
- Глава 2. Объектно-ориентированное проектирование и платформа NetBeans
- 2.1.Процедурное и объектно-ориентированное программирование. Инкапсуляция
- 2.2. Работа со ссылочными переменными. Сборка мусора
- 2.3. Проекты NetBeans. Пакеты. Уровни видимости классов. Импорт классов
- 2.4. Базовые пакеты и классы Java
- 2.5. Создание в NetBeans простейшего приложения Java
- 2.6. Компиляция файлов проекта и запуск приложения
- 2.7. Структура проекта NetBeans
- 2.8. Создание в NetBeans приложения Java с графическим интерфейсом
- 2.9. Редактор экранных форм
- 2.10. Внешний вид приложения
- 2.11. Ведение проектов
- 2.11. Редактирование меню экранной формы
- 2.12. Создание нового класса
- 2.13. Документирование исходного кода в Java
- 2.14. Основные компоненты пакетов swing и awt
- 2.15. Технологии Java и .Net
- Краткие итоги по главе 2
- Задания
- Глава 3. Примитивные типы данных и операторы для работы с ними
- 3.1.Булевский (логический) тип
- 3.2.Целые типы, переменные, константы
- 3.3.Основные операторы для работы с целочисленными величинами
- 3.4.Вещественные типы и класс Math
- 3.5.Правила явного и автоматического преобразования типа при работе с числовыми величинами
- 3.6. Оболочечные классы. Упаковка (boxing) и распаковка (unboxing)
- 3.7.Приоритет операторов
- 3.8.Типы-перечисления (enum)
- Краткие итоги по главе 3
- Задания
- Глава 4. Работа с числами в языке Java
- 4.1 Двоичное представление целых чисел Позиционные и непозиционные системы счисления
- Двоичное представление положительных целых чисел
- Двоичное представление отрицательных целых чисел. Дополнительный код
- Проблемы целочисленной машинной арифметики
- Шестнадцатеричное представление целых чисел и перевод из одной системы счисления в другую
- 4.2. Побитовые маски и сдвиги
- 4.3. Двоичное представление вещественных чисел Двоичные дроби
- Мантисса и порядок числа
- Стандарт ieee 754 представления чисел в формате с плавающей точкой*
- Краткие итоги по главе 4
- Задания
- Глава 5. Управляющие конструкции Составной оператор
- Условный оператор if
- Оператор выбора switch
- Условное выражение …?... : …
- Оператор цикла for
- Оператор цикла while – цикл с предусловием
- Оператор цикла do...While – цикл с постусловием
- Операторы прерывания continue, break, return, System.Exit
- Краткие итоги по главе 5
- Задания
- Глава 6. Начальные сведения об объектном программировании
- Наследование и полиморфизм. Uml-диаграммы
- Функции. Модификаторы. Передача примитивных типов в функции
- Локальные и глобальные переменные. Модификаторы доступа и правила видимости. Ссылка this
- Передача ссылочных типов в функции. Проблема изменения ссылки внутри подпрограммы
- Наследование. Суперклассы и подклассы. Переопределение методов
- Наследование и правила видимости. Зарезервированное слово super
- Статическое и динамическое связывание методов. Полиморфизм
- Базовый класс Object
- Конструкторы. Зарезервированные слова super и this. Блоки инициализации
- Удаление неиспользуемых объектов и метод finalize. Проблема деструкторов для сложно устроенных объектов
- Перегрузка методов
- Правила совместимости ссылочных типов как основа использования полиморфного кода. Приведение и проверка типов
- Рефакторинг
- Reverse engineering – построение uml-диаграмм по разработанным классам
- Краткие итоги по главе 6
- Задания
- Глава 7. Важнейшие объектные типы Массивы
- Коллекции, списки, итераторы
- Работа со строками в Java. Строки как объекты. Классы String, StringBuffer и StringBuilder
- Работа с графикой
- Исключительные ситуации Обработка исключительных ситуаций
- Иерархия исключительных ситуаций
- Объявление типа исключительной ситуации и оператор throw
- Объявление метода, который может возбуждать исключительную ситуацию. Зарезервированное слово throws
- Работа с файлами и папками
- Краткие итоги по главе 7
- Задания
- Глава 8. Наследование: проблемы и альтернативы. Интерфейсы. Композиция Проблемы множественного наследования классов. Интерфейсы
- Отличия интерфейсов от классов. Проблемы наследования интерфейсов
- Пример на использование интерфейсов
- Композиция как альтернатива множественному наследованию
- Краткие итоги по главе 8
- Задания
- Глава 9. Дополнительные элементы объектного программирования на языке Java Потоки выполнения (threads) и синхронизация
- Преимущества и проблемы при работе с потоками выполнения
- Синхронизация по ресурсам и событиям
- Класс Thread и интерфейс Runnable. Создание и запуск потока выполнения
- Поля и методы, заданные в классе Thread
- Подключение внешних библиотек dll.“Родные” (native) методы*
- Краткие итоги по главе 9
- Задания
- Глава 10. Введение в сетевое программирование Краткая справка по языку html
- Апплеты
- Сервлеты
- Технология jsp – Java Server Pages
- Краткие итоги по главе 10
- Задания
- Глава 11. Встроенные классы Виды встроенных классов
- Вложенные (nested) классы и интерфейсы
- Внутренние (inner) классы
- Локальные (local) классы
- Анонимные (anonimous) классы и обработчики событий
- Анонимные (anonimous) классы и слушатели событий (listeners)
- Краткие итоги по главе 11
- Задания
- Глава 12. Компонентное программирование Компонентная архитектура JavaBeans
- Мастер создания компонента в NetBeans
- Пример создания компонента в NetBeans – панель с заголовком
- Добавление в компонент новых свойств
- Добавление в компонент новых событий
- Краткие итоги по главе 12
- Задания
- Литература
- Дополнительная литература
- 276 Курс подготовлен при поддержке Sun Microsystems