Upcasting и Downcasting
Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy — урок 30 из 87.
В предыдущем уроке вы узнали, как переменная родительского типа может содержать объект дочернего класса: Animal myPet = new Dog(). Это присваивание называется восходящим преобразованием (upcasting), и оно происходит автоматически, так как Dog всегда является Animal.
Восходящее преобразование (Upcasting) выполняется вверх по иерархии наследования. Оно является неявным и всегда безопасно:
Dog dog = new Dog();
Animal animal = dog; // Upcasting - автоматическоеПри восходящем преобразовании (upcasting) вы можете получить доступ только к методам, определенным в родительском классе. Методы, специфичные для дочернего класса, становятся скрытыми, даже если объект по-прежнему является Dog внутри.
Понижающее приведение типов (Downcasting) происходит в обратном направлении, преобразуя ссылку родительского класса обратно к дочернему типу. Это требует явного приведения, поскольку это не всегда безопасно:
Animal animal = new Dog(); // Восходящее преобразование (Upcasting)
Dog dog = (Dog) animal; // Нисходящее преобразование (Downcasting) - требуется явное приведение типов
dog.bark(); // Теперь мы можем получить доступ к методам DogПонижающее приведение типов (downcasting) рискованно. Если объект на самом деле не является тем типом, к которому вы его приводите, Java выбрасывает исключение ClassCastException во время выполнения:
Animal animal = new Cat();
Dog dog = (Dog) animal; // Компилируется, но падает во время выполнения!Компилятор не может обнаружить эту ошибку, так как он видит только объявленные типы. Фактический тип объекта становится известен только во время выполнения программы. В следующем уроке вы узнаете, как безопасно проверять тип объекта перед понижающим приведением типов.
Задание
ЛегкоДавайте создадим систему медиаплеера, которая демонстрирует восходящее (upcasting) и нисходящее (downcasting) преобразование типов в действии. Вы создадите иерархию типов медиа и попрактикуетесь в преобразовании ссылок между родительскими и дочерними классами.
Вы организуете свой код в четырех файлах:
Media.java: Создайте базовый класс, который является общим для всех типов медиа. Каждый медиа-элемент имеет полеtitle(String). Включите конструктор для его инициализации, метод-геттерgetTitle()и методplay(), который выводит:"Playing: " + titleSong.java: Создайте класс, расширяющий Media. У песен есть дополнительное полеartist(String). Используйтеsuperдля инициализации родительского класса. Переопределитеplay(), чтобы вывести:"Playing song: " + title + " by " + artist. Добавьте методshowArtist(), который выводит:"Artist: " + artistPodcast.java: Создайте еще один класс, расширяющий Media. У подкастов есть полеepisode(int). Переопределитеplay(), чтобы вывести:"Playing podcast: " + title + " - Episode " + episode. Добавьте методshowEpisode(), который выводит:"Episode: " + episodeMain.java: Продемонстрируйте восходящее и нисходящее преобразование с вашими типами медиа. Вы получите четыре входных значения: название песни, имя исполнителя, название подкаста и номер эпизода.Сначала создайте объект Song и выполните восходящее преобразование (upcast) к ссылке типа Media. Вызовите
play()по этой ссылке, чтобы увидеть полиморфизм времени выполнения в действии.Затем выполните нисходящее преобразование (downcast) этой ссылки Media обратно к Song и вызовите
showArtist()для доступа к методу, специфичному для дочернего класса.Затем создайте Podcast и выполните восходящее преобразование к ссылке Media. Вызовите
play()по этой ссылке.Наконец, выполните нисходящее преобразование этой ссылки Media обратно к Podcast и вызовите
showEpisode().
Вы получите четыре входных значения: название песни (String), имя исполнителя (String), название подкаста (String) и номер эпизода (int).
Ваш вывод должен содержать четыре строки — две для песни (результат воспроизведения и информация об исполнителе) и две для подкаста (результат воспроизведения и информация об эпизоде). Обратите внимание, как восходящее преобразование позволяет вам единообразно обрабатывать различные типы медиа, в то время как нисходящее преобразование позволяет получить доступ к их уникальным функциям!
Шпаргалка
В Java восходящее преобразование (upcasting) перемещает вверх по иерархии наследования и происходит автоматически. Объект дочернего класса может быть присвоен переменной родительского типа:
Dog dog = new Dog();
Animal animal = dog; // Upcasting - автоматическиПри восходящем преобразовании вы можете обращаться только к методам, определенным в родительском классе, даже если объект по-прежнему относится к дочернему типу.
Нисходящее преобразование (downcasting) преобразует ссылку родительского типа обратно в дочерний тип. Оно требует явного приведения типов:
Animal animal = new Dog(); // Upcasting
Dog dog = (Dog) animal; // Downcasting - требуется явное приведение
dog.bark(); // Теперь мы можем обращаться к методам DogНисходящее преобразование рискованно. Если объект на самом деле не является тем типом, к которому вы его приводите, Java выбросит исключение ClassCastException во время выполнения:
Animal animal = new Cat();
Dog dog = (Dog) animal; // Компилируется, но вызывает ошибку во время выполнения!Компилятор не может обнаружить эту ошибку, так как он видит только объявленные типы. Фактический тип объекта становится известен только во время работы программы.
Попробуйте сами
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Чтение входных данных
String songTitle = scanner.nextLine();
String artistName = scanner.nextLine();
String podcastTitle = scanner.nextLine();
int episodeNumber = scanner.nextInt();
// TODO: Создайте Song и выполните восходящее приведение к ссылке Media
// TODO: Вызовите play() для ссылки Media (демонстрирует полиморфизм)
// TODO: Выполните нисходящее приведение ссылки Media обратно к Song
// TODO: Вызовите showArtist() для ссылки Song
// TODO: Создайте Podcast и выполните восходящее приведение к ссылке Media
// TODO: Вызовите play() для ссылки Media
// TODO: Выполните нисходящее приведение ссылки Media обратно к Podcast
// TODO: Вызовите showEpisode() для ссылки Podcast
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово thisМетодыПоля (атрибуты)Метод-конструкторПерегрузка конструкторовИтоги — Простой калькулятор4Наследование
Основы наследования (extends)Ключевое слово superПереопределение методов (@Override)Цепочка конструкторовКласс ObjectОдиночное и многоуровневое наследованиеПочему нет множественного наследования классовИтоги — Иерархия сотрудников7Специальные методы и класс Object
Метод toString()equals() и hashCode()Метод clone()compareTo() и ComparableИнтерфейс ComparatorПовторение — Кастомная сортировка2Модификаторы доступа и инкапсуляция
Обзор уровней доступаМетоды геттеры и сеттерыСокрытие данныхКлючевое слово finalИтоги — Менеджер банковского счета5Полиморфизм
Основы перегрузки методовПереопределение методов (Run-Time)Upcasting и DowncastingОператор instanceofАбстрактные классы и методыИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияАгрегация против композицииВнутренние, вложенные и анонимные классыEnums и методы перечисленийRecords (Java 16+)Sealed-классы (Java 17+)11Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy3Свойства класса и статические члены
Переменные экземпляра и статические переменныеСтатические методыСтатические блокиКонстанты (static final)Итоги: Счетчик и утилиты6Интерфейсы и абстрактные классы
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовDefault и Static методы в интерфейсахАбстрактные классы vs ИнтерфейсыФункциональные интерфейсыИтоги — Платежная система9Обобщения
Введение в обобщенияОбобщенные классыОбобщенные методыОграниченные параметры типаСимволы подстановки (Wildcards: ?, extends, super)Итоги — Обобщенный контейнер12Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»