JavaRush /Курсы /JAVA 25 SELF /Проблемы полиморфизма и абстракций

Проблемы полиморфизма и абстракций

JAVA 25 SELF
23 уровень , 3 лекция
Открыта

1. Полиморфизм: что это и зачем нужен

Если вы думаете, что полиморфизм — это что-то из мира мутантов Marvel, спешу разочаровать: в программировании всё куда спокойнее, но не менее волшебно. Полиморфизм — это способность объектов с разной реализацией реагировать на одни и те же вызовы методов по-разному.

Пример из жизни:
У вас есть класс Book и класс Magazine, оба наследуются от абстрактного класса LibraryItem. Вы хотите, чтобы для любого элемента библиотеки можно было вызвать метод printInfo(), и он выведет нужную информацию — для книги это будет автор и название, для журнала — номер выпуска и дата.

Пример кода:

abstract class LibraryItem {
    String title;

    LibraryItem(String title) {
        this.title = title;
    }

    abstract void printInfo();
}

class Book extends LibraryItem {
    String author;

    Book(String title, String author) {
        super(title);
        this.author = author;
    }

    @Override
    void printInfo() {
        System.out.println("Книга: " + title + ", автор: " + author);
    }
}

class Magazine extends LibraryItem {
    int issueNumber;

    Magazine(String title, int issueNumber) {
        super(title);
        this.issueNumber = issueNumber;
    }

    @Override
    void printInfo() {
        System.out.println("Журнал: " + title + ", выпуск: " + issueNumber);
    }
}

Теперь можно создать массив из разных элементов и вызвать printInfo() для каждого:

LibraryItem[] items = {
    new Book("Повелитель мух", "Вильям Голдинг"),
    new Magazine("Наука и жизнь", 5)
};

for (LibraryItem item : items) {
    item.printInfo();
}
// Выведет:
// Книга: Повелитель мух, автор: Вильям Голдинг
// Журнал: Наука и жизнь, выпуск: 5

Вот так работает полиморфизм!

2. Типичные ошибки с полиморфизмом

Попытка вызвать методы, которых нет в базовом типе

Одна из самых частых ошибок — попытка обратиться к методу, который объявлен только в дочернем классе, через ссылку на базовый тип.

LibraryItem item = new Book("Гарри Поттер", "Дж. Роулинг");
// item.getAuthor(); // Ошибка компиляции! В LibraryItem нет метода getAuthor()

Java компилирует код, исходя из того, что видит в типе переменной (LibraryItem), а не в реальном объекте (Book). Поэтому если вам нужно вызвать специфичный для книги метод, нужно привести тип:

if (item instanceof Book) {
    Book book = (Book) item;
    // Теперь можно вызвать book.getAuthor()
}

Приведение типов без проверки

Если вы уверены, что объект — это Book, но на самом деле это не так, получите ClassCastException в рантайме. Например:

LibraryItem item = new Magazine("Forbes", 12);
Book book = (Book) item; // БУМ! ClassCastException

Правильный способ — всегда проверять тип:

if (item instanceof Book) {
    Book book = (Book) item;
    // ОК
} else {
    System.out.println("Это не книга!");
}

Неиспользование преимуществ полиморфизма

Иногда разработчики пишут код так, что он жёстко привязан к конкретным типам, хотя можно было бы использовать абстракции. Например, если вы пишете:

Book[] books = ...;
for (Book book : books) {
    book.printInfo();
}

Это работает только для книг. А если завтра появятся журналы, газеты, комиксы? Лучше использовать массив LibraryItem[] и работать с методами базового класса или интерфейса.

3. Абстракции: зачем они нужны и как их не испортить

Абстрактные классы и интерфейсы

Абстракция — это искусство выделять главное и скрывать детали. В Java для этого есть абстрактные классы и интерфейсы.

  • Абстрактный класс — это класс, который не может быть создан напрямую, а только унаследован.
  • Интерфейс — это контракт: что должен уметь класс, но не как он это делает.

Ошибка 1: Создание абстрактного класса без абстрактных методов

Если в вашем абстрактном классе нет ни одного абстрактного метода, задумайтесь — а точно ли он должен быть абстрактным? Может, проще сделать обычный класс?

abstract class UselessAbstract {
    void sayHello() {
        System.out.println("Hello!");
    }
}
// Лучше сделать обычный класс, если нет абстрактных методов

Ошибка 2: Отсутствие реализации обязательных методов в наследниках

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

class Magazine extends LibraryItem {
    Magazine(String title, int issueNumber) {
        super(title);
        // ...
    }

    @Override
    void printInfo() {
        // Пусто! Плохо!
    }
}

Ошибка 3: Слишком глубокая или запутанная иерархия абстракций

Когда классы наследуются друг от друга в пять–десять уровней, разобраться в них становится очень сложно. Лучше делать "плоские" иерархии, где всё понятно.

Плохой пример:

LibraryItem
  |
  BookItem
    |
    PrintedBook
      |
      IllustratedBook
        |
        ChildrenIllustratedBook

Сложно, не правда ли? Лучше ограничиться двумя-тремя уровнями.

4. Практика: Применение полиморфизма и абстракций в учебном приложении

Давайте доработаем ваше учебное приложение для библиотеки. Ранее у вас были только книги. Теперь добавим журналы и реализуем общий интерфейс для печатных изданий.

Объявим абстрактный класс:

abstract class LibraryItem {
    protected String title;

    public LibraryItem(String title) {
        this.title = title;
    }

    public abstract void printInfo();
}

Добавим дочерние классы:

class Book extends LibraryItem {
    private String author;

    public Book(String title, String author) {
        super(title);
        this.author = author;
    }

    @Override
    public void printInfo() {
        System.out.println("Книга: " + title + ", автор: " + author);
    }
}

class Magazine extends LibraryItem {
    private int issueNumber;

    public Magazine(String title, int issueNumber) {
        super(title);
        this.issueNumber = issueNumber;
    }

    @Override
    public void printInfo() {
        System.out.println("Журнал: " + title + ", выпуск: " + issueNumber);
    }
}

Используем полиморфизм:

LibraryItem[] items = {
    new Book("Чистый код", "Роберт Мартин"),
    new Magazine("Java World", 3)
};

for (LibraryItem item : items) {
    item.printInfo();
}

Добавим интерфейс для электронных изданий

Допустим, некоторые издания можно читать онлайн. Введём интерфейс:

interface ReadableOnline {
    void openOnline();
}

class EBook extends Book implements ReadableOnline {
    private String url;

    public EBook(String title, String author, String url) {
        super(title, author);
        this.url = url;
    }

    @Override
    public void openOnline() {
        System.out.println("Открываем электронную книгу по адресу: " + url);
    }
}

Теперь можно работать с электронными книгами через интерфейс:

ReadableOnline ebook = new EBook("Java для чайников", "Барри Берд", "https://example.com/java");
ebook.openOnline();

5. Как избежать проблем с полиморфизмом и абстракциями: best practices

  • Используйте интерфейсы и абстрактные классы для описания поведения, а не состояния.
    Например, интерфейс Printable хорошо описывает возможность "печати", а вот хранить в интерфейсе поле String title — уже плохая идея.
  • Проверяйте тип объекта с помощью instanceof перед приведением.
    Особенно если объект может быть разного типа. Это убережёт от ClassCastException.
  • Стремитесь к "плоским" и понятным иерархиям.
    Чем проще дерево наследования — тем легче поддерживать и расширять код.
  • Старайтесь не делать "бессмысленных" абстракций.
    Если класс не содержит абстрактных методов и не предназначен для наследования — не делайте его абстрактным.
  • Используйте аннотацию @Override всегда, когда переопределяете метод.
    Это помогает компилятору ловить ошибки в сигнатуре.

6. Типичные ошибки при работе с полиморфизмом и абстракциями

Ошибка № 1: Приведение типа без проверки

Иногда хочется "срезать угол" и привести тип без проверки. Это может сработать, а может привести к внезапному падению программы. Всегда используйте instanceof:

if (item instanceof Book) {
    Book book = (Book) item;
    // ...
}

Ошибка № 2: Попытка вызвать метод дочернего класса через ссылку на базовый тип

LibraryItem item = new Book("Java", "Автор");
item.getAuthor(); // Ошибка компиляции: в LibraryItem нет такого метода!

Решение — либо привести тип, либо добавить нужный метод в базовый класс (если это логично).

Ошибка № 3: Неполная реализация интерфейса или абстрактного класса

Если забыть реализовать все методы интерфейса — компилятор не даст собрать проект. Но если реализовать "заглушки", которые ничего не делают, это приведёт к неожиданному поведению.

Ошибка № 4: Слишком глубокая иерархия наследования

Если у вас больше трёх уровней наследования — задумайтесь, нельзя ли упростить архитектуру.

Ошибка № 5: Нарушение принципа единой ответственности

Если абстракция описывает слишком много обязанностей, она становится трудноподдерживаемой. Лучше разбивать на несколько интерфейсов или классов.

1
Задача
JAVA 25 SELF, 23 уровень, 3 лекция
Недоступна
Звуки животных: Когда собака гавкает 🐕
Звуки животных: Когда собака гавкает 🐕
1
Задача
JAVA 25 SELF, 23 уровень, 3 лекция
Недоступна
Виртуальный холст: Разнообразие фигур 🎨
Виртуальный холст: Разнообразие фигур 🎨
1
Задача
JAVA 25 SELF, 23 уровень, 3 лекция
Недоступна
Умный дом: Кипящий чайник 💡
Умный дом: Кипящий чайник 💡
1
Задача
JAVA 25 SELF, 23 уровень, 3 лекция
Недоступна
IT-Компания: Программист, который работает и отчитывается 👨‍💻
IT-Компания: Программист, который работает и отчитывается 👨‍💻
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
nightmarakesh Уровень 1
17 июля 2026
Вообще как будто цель этого уровня остается только в задумках авторов курса. Казалось что это будет и правда "best practics" принципов ООП, уже на примерах реальных проектов, где можно будет подцепить для себя что новое или расширить понимание на основе уже полученных знаний. Вместо этого это повтор первых лекций, по ощущениям направленные на тех кто их пропустил, ибо задачки опять в два действия на те же темы, в лекциях ноль нового. Вопрос зачем тогда вообще она здесь?
nightmarakesh Уровень 1
17 июля 2026
Код пример в начале полностью соответствует тому что в середине, хотя подводится как к чему то новому:

Давайте доработаем ваше учебное приложение для библиотеки. Ранее у вас были только книги. Теперь добавим журналы и реализуем общий интерфейс для печатных изданий.
Где у нас были "только книги" видимо когда-то давно:) После таких примеров реально складывается ощущение что большую часть лекций пишет ИИ или человек с амнезией..