Разбираем extends и implements в Java: чем отличаются, как работают, почему класс может реализовать много интерфейсов. Примеры кода и ответы на вопросы собеседований.
В предыдущей статье мы разобрали, что такое интерфейс и почему он выступает контрактом между клиентом и поставщиком. Теперь пора понять одну из самых популярных тем на собеседованиях по Java: в чём разница между extends и implements? Может ли класс наследовать несколько классов? Может ли класс реализовать несколько интерфейсов? Почему методы интерфейса всегда public abstract? Почему переменные интерфейса всегда public static final? Эти концепции лежат в основе множественного наследования в Java и широко используются в таких фреймворках, как Spring Boot, Hibernate и Java Collections Framework.
Java поддерживает два вида наследования:
Чтобы их различать, Java предоставляет два ключевых слова: extends и implements.
Ключевое слово extends используется при наследовании от класса или расширении другого интерфейса. Ключевое слово implements используется, когда класс предоставляет реализацию для интерфейса.
| Ключевое слово | Используется с | Назначение |
|---|---|---|
| extends | Класс → Класс, Интерфейс → Интерфейс | Унаследовать существующее поведение |
| implements | Класс → Интерфейс | Предоставить реализацию контракта |
Java не поддерживает множественное наследование классов.
class Employee {
public void work() {
System.out.println("Сотрудник работает.");
}
}
class Manager extends Employee {
// Manager наследует метод work()
}
Почему класс не может расширить несколько классов? Предположим, Java бы это разрешила:
class A {
public void display() {
System.out.println("Класс A");
}
}
class B {
public void display() {
System.out.println("Класс B");
}
}
class C extends A, B { // ❌ Ошибка
}
Это называется проблемой ромба (Diamond Problem). Чтобы избежать неоднозначности, Java позволяет классу расширять только один класс.
В отличие от классов, интерфейсы содержат только контракты, а не реализации. Поэтому неоднозначности нет.
interface PaymentService {
void processPayment();
}
interface NotificationService {
void sendNotification();
}
class OnlineOrderService implements PaymentService, NotificationService {
@Override
public void processPayment() {
System.out.println("Платёж обработан.");
}
@Override
public void sendNotification() {
System.out.println("Уведомление отправлено.");
}
}
Java допускает комбинацию обоих ключевых слов.
interface PaymentService {
void processPayment();
}
interface NotificationService {
void sendNotification();
}
class Order {
public void displayOrder() {
System.out.println("Отображение заказа.");
}
}
class OnlineOrder extends Order implements PaymentService, NotificationService {
@Override
public void processPayment() {
System.out.println("Оплата завершена.");
}
@Override
public void sendNotification() {
System.out.println("Клиент уведомлён.");
}
}
Это очень распространённый дизайн в корпоративных приложениях.
Интерфейсы поддерживают множественное наследование.
interface PaymentService {
void processPayment();
}
interface NotificationService {
void sendNotification();
}
interface OrderService extends PaymentService, NotificationService {
// OrderService наследует оба метода
}
| Выражение | Допустимо? | Объяснение |
|---|---|---|
| class B extends A | ✅ | Класс наследует другой класс |
| interface B extends A | ✅ | Интерфейс расширяет интерфейс |
| class C implements A | ✅ | Класс реализует интерфейс |
| class C extends A implements B | ✅ | Расширить один класс и реализовать интерфейсы |
| interface C extends A, B | ✅ | Интерфейс расширяет несколько интерфейсов |
| class C implements A extends B | ❌ | Неверный синтаксис |
Каждый метод, объявленный внутри интерфейса, неявно является public и abstract. Следующие объявления эквивалентны:
interface PaymentService {
void processPayment();
}
// Компилятор добавляет public abstract
interface PaymentService {
public abstract void processPayment();
}
Почему методы интерфейса public? Интерфейсы представляют контракты. Любой реализующий класс, независимо от пакета, должен иметь доступ к этим методам. Почему abstract? Интерфейсы определяют что должно произойти, а реализующие классы — как это происходит.
Недопустимые модификаторы для традиционных абстрактных методов интерфейса: private, protected, final, synchronized, native. Например:
interface PaymentService {
final void processPayment(); // ❌ Ошибка компиляции
}
Примечание: начиная с Java 8, интерфейсы могут содержать default и static методы, а с Java 9 — private методы. В этой статье мы сосредоточились на традиционных абстрактных методах.
Интерфейсы могут содержать переменные, но каждая переменная интерфейса неявно является public static final.
interface ApplicationConstants {
int MAX_USERS = 100;
}
// Компилятор обрабатывает как:
// public static final int MAX_USERS = 100;
Почему public? Каждый реализующий класс должен иметь доступ к константе. Почему static? Значение принадлежит самому интерфейсу, объект не требуется. Почему final? Значение никогда не должно меняться, реализующие классы не могут его изменить.
Переменные интерфейса должны быть инициализированы. Неправильно:
interface ApplicationConstants {
int MAX_USERS; // ❌ Ошибка: = expected
}
Правильно:
interface ApplicationConstants {
int MAX_USERS = 100;
}
Переменные интерфейса нельзя изменить.
interface ApplicationConstants {
int MAX_USERS = 100;
}
class Test implements ApplicationConstants {
public static void main(String[] args) {
// MAX_USERS = 200; ❌ Ошибка компиляции
System.out.println(MAX_USERS); // 100
}
}
class C extends A, B {} — Java не позволяет.MAX_USERS = 500; — ошибка компиляции, так как переменная final.ApplicationConstants.MAX_USERS.Запомните EIM:
Думайте EIM, когда видите наследование в Java.
Если это руководство было полезным, поставьте ❤️ и подпишитесь для получения новых уроков по Java, вопросов с собеседований и практических примеров кода. Удачи в программировании!
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →