Переопределение методов (Run-Time)
Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy — урок 29 из 87.
В то время как перегрузка методов разрешается на этапе компиляции, переопределение методов демонстрирует полиморфизм времени выполнения. Здесь Java решает, какой метод выполнить во время фактической работы программы, основываясь на фактическом типе объекта, а не на объявленном типе переменной.
Вы узнали о переопределении методов в главе «Наследование». Полиморфным его делает то, как Java обрабатывает ситуацию, когда ссылка родительского класса указывает на объект дочернего класса:
public class Animal {
public void speak() {
System.out.println("Some sound");
}
}
public class Dog extends Animal {
@Override
public void speak() {
System.out.println("Bark!");
}
}
public class Cat extends Animal {
@Override
public void speak() {
System.out.println("Meow!");
}
}Теперь посмотрите, что происходит при использовании ссылок родительского типа:
Animal myPet = new Dog();
myPet.speak(); // Вывод: Bark!
myPet = new Cat();
myPet.speak(); // Вывод: Meow!Несмотря на то, что myPet объявлена как Animal, Java вызывает переопределенный метод на основе фактического типа объекта во время выполнения. Это называется динамической диспетчеризацией методов. JVM смотрит на то, чем объект является на самом деле, а не на то, что говорит переменная.
Это позволяет использовать мощные паттерны, такие как единообразная обработка различных объектов:
Animal[] pets = {new Dog(), new Cat(), new Dog()};
for (Animal pet : pets) {
pet.speak(); // Каждый вызывает свою версию
}Полиморфизм времени выполнения позволяет писать гибкий код, который работает с родительскими типами, автоматически используя при этом правильное поведение дочерних типов.
Задание
ЛегкоДавайте создадим систему уведомлений, которая демонстрирует полиморфизм времени выполнения в действии. Вы создадите иерархию типов уведомлений, где один и тот же вызов метода производит разные результаты в зависимости от фактического типа объекта во время выполнения.
Вы организуете свой код в четырех файлах:
Notification.java: Создайте базовый класс, который является общим для всех уведомлений. Каждое уведомление имеет полеrecipient(String). Включите конструктор для его инициализации, метод-геттер и методsend(), который выводит:Sending notification to [recipient]EmailNotification.java: Создайте класс, который расширяет Notification. Email-уведомления добавляют полеsubject. Используйтеsuperдля инициализации родительского класса. Переопределите методsend(), чтобы он выводил:Emailing [recipient]: [subject]SMSNotification.java: Создайте еще один класс, который расширяет Notification. SMS-уведомления добавляют полеphoneNumber. Переопределитеsend(), чтобы он выводил:Texting [phoneNumber] for [recipient]Main.java: Здесь полиморфизм времени выполнения проявляется во всей красе! Вы получите три входных значения: имя получателя, тему письма и номер телефона. Создайте массив ссылокNotification, содержащий три объекта в следующем порядке: базовый Notification, EmailNotification и SMSNotification (все используют одного и того же получателя). Затем пройдите циклом по массиву и вызовитеsend()для каждого элемента. Посмотрите, как Java автоматически вызывает правильную переопределенную версию в зависимости от того, чем на самом деле является каждый объект!
Вы получите три входных значения: имя получателя (String), тему письма (String) и номер телефона (String).
Ваш вывод должен содержать три строки — каждый вызов send() производит разный результат, даже если вы вызываете один и тот же метод по ссылкам Notification. Это и есть динамическая диспетчеризация методов в действии!
Шпаргалка
Переопределение методов демонстрирует полиморфизм времени выполнения, при котором Java решает, какой метод выполнить во время работы программы, основываясь на фактическом типе объекта, а не на объявленном типе переменной.
Когда ссылка родительского класса указывает на объект дочернего класса, Java вызывает переопределенный метод на основе фактического типа объекта во время выполнения:
public class Animal {
public void speak() {
System.out.println("Some sound");
}
}
public class Dog extends Animal {
@Override
public void speak() {
System.out.println("Bark!");
}
}
Animal myPet = new Dog();
myPet.speak(); // Output: Bark!
myPet = new Cat();
myPet.speak(); // Output: Meow!Это называется динамической диспетчеризацией методов. JVM смотрит на то, чем объект является на самом деле, а не на то, что говорит переменная.
Полиморфизм времени выполнения позволяет единообразно обрабатывать различные объекты:
Animal[] pets = {new Dog(), new Cat(), new Dog()};
for (Animal pet : pets) {
pet.speak(); // Each calls its own version
}Попробуйте сами
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Чтение входных данных
String recipient = scanner.nextLine();
String subject = scanner.nextLine();
String phoneNumber = scanner.nextLine();
// TODO: Создайте массив ссылок Notification из 3 элементов
// Массив должен содержать (в указанном порядке):
// 1. Базовый объект Notification
// 2. Объект EmailNotification
// 3. Объект SMSNotification
// Все объекты используют одного и того же получателя (recipient)
// TODO: Пройдите циклом по массиву и вызовите send() для каждого элемента
// Это демонстрирует полиморфизм времени выполнения!
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»