Menu
Coddy logo textTech

Upcasting и Downcasting

Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy. Урок 30 из 87.

В предыдущем уроке вы увидели, как переменная типа parent может хранить дочерний объект: Animal myPet = new Dog(). Это присваивание называется upcasting и происходит автоматически, потому что Dog всегда является Animal.

Восходящее приведение перемещается вверх по иерархии наследования. Оно выполняется неявно и всегда безопасно:

Dog dog = new Dog();
Animal animal = dog;  // Апкастинг - автоматический

При восходящем приведении можно получить доступ только к методам, определённым в классе parent. Методы, специфичные для дочернего класса, становятся скрытыми, хотя объект под капотом всё ещё является Dog.

Downcasting работает в обратном направлении, преобразуя ссылку parent обратно в дочерний тип. Для этого требуется явное преобразование cast, поскольку это не всегда безопасно:

Animal animal = new Dog();  // Восходящее приведение
Dog dog = (Dog) animal;     // Нисходящее приведение — требуется явное приведение
dog.bark();                 // Теперь мы можем обращаться к методам Dog

Downcasting — рискованная операция. Если объект на самом деле не является типом, к которому вы выполняете приведение, Java выдаёт ClassCastException во время выполнения:

Animal animal = new Cat();
Dog dog = (Dog) animal;  // Компилируется, но падает во время выполнения!

Компилятор не может обнаружить эту ошибку, потому что он видит только объявленные типы. Фактический тип объекта становится известен только во время выполнения программы. В следующем уроке ты узнаешь, как безопасно проверить тип объекта перед Downcasting.

challenge icon

Задание

Легко

Давайте создадим систему медиаплеера, демонстрирующую на практике восходящее и нисходящее приведение типов. Вы создадите иерархию типов медиа и попрактикуетесь в преобразовании ссылок между parent и дочерними классами.

Вы распределите свой код по четырём файлам:

  • Media.java: создайте базовый класс, общий для всех типов медиа. Каждый медиаобъект имеет поле title (String). Добавьте конструктор для его инициализации, getter getTitle() и метод play(), который выводит: Playing: [title]
  • Song.java: создайте класс, который extends Media. У песен есть дополнительное поле artist (String). Используйте super для инициализации parent. Переопределите play(), чтобы он выводил: Playing song: [title] by [artist]. Добавьте метод showArtist(), который выводит: Artist: [artist]
  • Podcast.java: создайте ещё один класс, который extends Media. У подкастов есть поле episode (int). Переопределите play(), чтобы он выводил: Playing podcast: [title] - Episode [episode]. Добавьте метод showEpisode(), который выводит: Episode: [episode]
  • Main.java: продемонстрируйте восходящее и нисходящее приведение типов для ваших типов медиа. Вы получите четыре входных значения: название песни, имя исполнителя, название подкаста и номер эпизода.

    Сначала создайте Song и выполните восходящее приведение к ссылке Media. Вызовите play() для этой ссылки, чтобы увидеть полиморфизм времени выполнения в действии.

    Затем выполните нисходящее приведение этой ссылки Media обратно к Song и вызовите showArtist(), чтобы получить доступ к методу, специфичному для дочернего класса.

    После этого создайте Podcast и выполните восходящее приведение к ссылке Media. Вызовите play() для этой ссылки.

    Наконец, выполните нисходящее приведение этой ссылки Media обратно к Podcast и вызовите showEpisode().

Вы получите четыре входных значения: название песни (String), имя исполнителя (String), название подкаста (String) и номер эпизода (int).

Ваша программа должна вывести четыре строки — две для песни (результат вызова play и информация об исполнителе) и две для подкаста (результат вызова play и информация об эпизоде). Обратите внимание, как восходящее приведение позволяет работать с разными типами медиа единообразно, а нисходящее приведение — получать доступ к их уникальным возможностям!

Попробуйте сами

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 и выполнить upcast к ссылке Media
        
        // TODO: Вызвать play() на ссылке Media (демонстрирует полиморфизм)
        
        // TODO: Выполнить downcast ссылки Media обратно к Song
        
        // TODO: Вызвать showArtist() на ссылке Song
        
        // TODO: Создать Podcast и выполнить upcast к ссылке Media
        
        // TODO: Вызвать play() на ссылке Media
        
        // TODO: Выполнить downcast ссылки Media обратно к Podcast
        
        // TODO: Вызвать showEpisode() на ссылке Podcast
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Java