Menu
Coddy logo textTech

Переопределение методов (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();  // Каждый вызывает свою версию
}

Полиморфизм времени выполнения позволяет писать гибкий код, который работает с родительскими типами, автоматически используя при этом правильное поведение дочерних типов.

challenge icon

Задание

Легко

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

Вы организуете свой код в четырех файлах:

  • 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() для каждого элемента
        // Это демонстрирует полиморфизм времени выполнения!
    }
}
quiz iconПроверьте себя

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

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