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).
Ваш вывод должен содержать четыре строки — две для песни (результат воспроизведения и информация об исполнителе) и две для подкаста (результат воспроизведения и информация об эпизоде). Обратите внимание, как восходящее преобразование позволяет вам единообразно обрабатывать различные типы медиа, в то время как нисходящее преобразование позволяет получить доступ к их уникальным функциям!
Попробуйте сами
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
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»