Абстрактные классы vs Интерфейсы
Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy — урок 38 из 87.
Теперь, когда вы понимаете как абстрактные классы, так и интерфейсы, возникает распространенный вопрос: когда следует использовать одно вместо другого? Выбор зависит от того, что именно вы пытаетесь смоделировать.
Используйте абстрактный класс, когда классы имеют общую базу с общим состоянием или поведением. Абстрактные классы могут иметь переменные экземпляра, конструкторы и сочетание абстрактных и конкретных методов:
public abstract class Animal {
protected String name; // общее состояние
public Animal(String name) {
this.name = name;
}
public void sleep() { // общее поведение
System.out.println(name + " is sleeping");
}
public abstract void makeSound(); // должен быть реализован
}Используйте интерфейс, когда вы хотите определить возможность, которую могут разделять несвязанные классы. Интерфейсы фокусируются на том, что объект может делать, а не на том, чем он является:
public interface Flyable {
void fly();
}
// Несвязанные классы могут обладать этой способностью
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }Вот краткое сравнение:
| Особенность | Абстрактный класс | Интерфейс |
|---|---|---|
| Переменные экземпляра | Да | Только константы |
| Конструкторы | Да | Нет |
| Множественное наследование | Нет (одиночное extends) | Да (множественное implements) |
| Модификаторы доступа | Любые | Только public (для абстрактных методов) |
Практическая рекомендация: если вы обнаружите, что создаете абстрактный класс только с абстрактными методами и без состояния, интерфейс, скорее всего, будет лучшим выбором.
Задание
ЛегкоДавайте создадим систему транспортных средств, которая продемонстрирует, когда использовать абстрактные классы, а когда — интерфейсы. Вы будете моделировать транспортные средства, которые разделяют общее состояние и поведение через абстрактный класс, добавляя при этом дополнительные возможности через интерфейсы.
Вы организуете свой код в пяти файлах:
Vehicle.java: Создайте абстрактный класс, который послужит основой для всех транспортных средств. У каждого транспортного средства есть полеbrand(String) и полеyear(int). Включите конструктор для инициализации обоих полей, методы-геттеры для каждого из них и абстрактный методstartEngine(), который возвращает String. Также добавьте конкретный методgetInfo(), который возвращает:[brand] ([year]). Это идеальный случай использования абстрактного класса — транспортные средства разделяют состояние (brand, year) и некоторое поведение (getInfo), но каждое запускает двигатель по-своему.Convertible.java: Определите интерфейс для транспортных средств, которые могут трансформировать свою крышу. Эта возможность не связана с тем, чем транспортное средство является — это то, что определенные транспортные средства могут делать. Объявите два метода:openRoof()иcloseRoof(), оба возвращающие String.Car.java: Создайте класс, который расширяетVehicleи реализуетConvertible. У Car есть дополнительное полеnumDoors(int). Используйтеsuperдля инициализации унаследованных полей. РеализуйтеstartEngine(), чтобы он возвращал:[brand] car engine started. РеализуйтеopenRoof(), чтобы он возвращал:[brand] roof opening, иcloseRoof(), чтобы он возвращал:[brand] roof closing.Motorcycle.java: Создайте класс, который расширяетVehicle, но НЕ реализуетConvertible— у мотоциклов нет крыш! У Motorcycle есть полеhasSidecar(boolean). РеализуйтеstartEngine(), чтобы он возвращал:[brand] motorcycle engine roaring.Main.java: Соберите все вместе, чтобы продемонстрировать разницу между абстрактными классами и интерфейсами. Вы получите четыре входных значения: марку автомобиля, год выпуска автомобиля, марку мотоцикла и год выпуска мотоцикла.Создайте Car (с 4 дверями) и Motorcycle (без коляски). Сначала продемонстрируйте общее поведение абстрактного класса, выведя
getInfo()для обоих транспортных средств — они оба наследуют это от Vehicle. Затем выведите результатstartEngine()для каждого — обратите внимание, как каждый тип транспортного средства реализует это по-разному.Наконец, продемонстрируйте возможности интерфейса: так как только Car реализует
Convertible, вызовите и выведитеopenRoof(), а затемcloseRoof()только для автомобиля.
Вы получите четыре входных значения: марку автомобиля (String), год выпуска автомобиля (int), марку мотоцикла (String) и год выпуска мотоцикла (int).
Ваш вывод должен содержать всего шесть строк. Обратите внимание, как оба транспортных средства разделяют состояние абстрактного класса и метод getInfo(), но только Car обладает возможностями кабриолета — это иллюстрирует, когда использовать каждый из подходов!
Шпаргалка
Используйте абстрактный класс, когда классы имеют общую базу с общим состоянием или поведением. Абстрактные классы могут иметь переменные экземпляра, конструкторы и сочетание абстрактных и конкретных методов:
public abstract class Animal {
protected String name; // общее состояние
public Animal(String name) {
this.name = name;
}
public void sleep() { // общее поведение
System.out.println(name + " is sleeping");
}
public abstract void makeSound(); // должен быть реализован
}Используйте интерфейс, когда вы хотите определить возможность, которую могут разделять несвязанные классы. Интерфейсы фокусируются на том, что объект может делать, а не на том, чем он является:
public interface Flyable {
void fly();
}
// Несвязанные классы могут разделять эту возможность
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }| Особенность | Абстрактный класс | Интерфейс |
|---|---|---|
| Переменные экземпляра | Да | Только константы |
| Конструкторы | Да | Нет |
| Множественное наследование | Нет (одиночное extends) | Да (множественное implements) |
| Модификаторы доступа | Любые | Только public (для абстрактных методов) |
Рекомендация: Если вы создаете абстрактный класс только с абстрактными методами и без состояния, интерфейс, скорее всего, будет лучшим выбором.
Попробуйте сами
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Чтение входных данных
String carBrand = scanner.nextLine();
int carYear = scanner.nextInt();
scanner.nextLine(); // потребление символа новой строки
String motorcycleBrand = scanner.nextLine();
int motorcycleYear = scanner.nextInt();
// TODO: Создать Car с 4 дверями
// TODO: Создать Motorcycle без коляски (false)
// TODO: Вывести getInfo() для обоих транспортных средств (демонстрирует поведение общего абстрактного класса)
// TODO: Вывести startEngine() для обоих транспортных средств (демонстрирует различные реализации)
// TODO: Вывести openRoof() и closeRoof() только для автомобиля (демонстрирует возможности интерфейса)
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»