Implements и extends в Java: когда использовать и в чем разница?
Ключевая разница: класс vs интерфейс
Главное, что нужно запомнить: extends используется для наследования от класса, а implements — для реализации интерфейса. Это две разные концепции, хотя обе относятся к наследованию.
extends (класс): вы наследуете реализацию (код методов и поля). Класс может расширять только один класс (одиночное наследование). Дочерний класс получает всё, что есть в родителе, и может это переопределить.
implements (интерфейс): вы берёте на себя обязательство реализовать определённые методы. Класс может реализовать множество интерфейсов (множественное наследование типа). Интерфейс — это контракт: он говорит "у тебя должны быть эти методы", но не даёт готового кода.
Исправляем ваш пример
// Класс — используем extends
class Animal {
void eat() {
System.out.println("Ест");
}
}
// Интерфейс — используем implements
interface Pet {
void play(); // все методы интерфейса по умолчанию public
}
// Dog наследует реализацию Animal и берёт обязательство реализовать Pet
class Dog extends Animal implements Pet {
// В интерфейсе метод public, поэтому и здесь обязательно public
@Override
public void play() {
System.out.println("Играет с мячом");
}
}
Ошибка была в том, что метод play() в классе Dog имел доступ по умолчанию (package-private), а интерфейс требует public. В Java 8+ у методов интерфейса может быть реализация (default methods), но сигнатура всё равно должна совпадать.
Ответы на ваши вопросы
- Почему для Animal extends, а для Pet implements? Потому что Animal — это класс с реализацией, а Pet — интерфейс без реализации. Вы не можете "расширить" интерфейс через extends (хотя интерфейсы могут расширять другие интерфейсы через extends).
- Можно ли implements Animal и extends Pet? Нет.
implementsработает только с интерфейсами,extends— с классами (или интерфейсами, если речь о наследовании интерфейсов). Попытка написатьclass Cat implements Animalвызовет ошибку компиляции, так как Animal — это класс. - Когда что выбирать? Используйте
extends, когда нужно переиспользовать готовую логику и поля родительского класса. Используйтеimplements, когда нужно гарантировать, что класс поддерживает определённое поведение, но реализация может быть разной.
Практический пример
// Базовый класс с реализацией
class Vehicle {
protected String brand;
void start() { System.out.println("Двигатель запущен"); }
}
// Интерфейсы — контракты
interface Flyable {
void fly();
}
interface Floatable {
void floatOnWater();
}
// Множественная реализация интерфейсов
class AmphibiousCar extends Vehicle implements Flyable, Floatable {
@Override
public void fly() { System.out.println("Летит, как самолёт"); }
@Override
public void floatOnWater() { System.out.println("Плывёт, как лодка"); }
// start() уже унаследован от Vehicle
}
В этом примере AmphibiousCar наследует готовую функциональность Vehicle (двигатель, бренд) и одновременно реализует два разных контракта: умение летать и плавать. Без интерфейсов пришлось бы дублировать код или использовать сложные иерархии.
Короткое правило
- extends = "является разновидностью" (is-a) + "имеет готовый код"
- implements = "обладает способностью" (can-do) + "обязуется реализовать"
Альтернативный взгляд: наследование поведения vs наследование типа
Можно посмотреть на это с другой стороны. extends даёт вам наследование реализации — вы получаете готовые методы и поля. Это мощно, но опасно: изменение родительского класса может сломать потомков (проблема хрупкого базового класса). implements даёт наследование типа — вы говорите "я могу делать X", но как именно — ваше дело.
На практике многие опытные разработчики рекомендуют отдавать предпочтение интерфейсам (implements), а не наследованию классов. Почему? Потому что интерфейсы:
- Позволяют множественное наследование (один класс может реализовать 10 интерфейсов)
- Не создают жёсткой связи между классами
- Легче тестировать (можно подменить реализацию)
Пример из Java Collections Framework: ArrayList использует extends AbstractList (переиспользует код) и implements List, RandomAccess, Cloneable (гарантирует поведение). Обратите внимание: интерфейсов много, а родительский класс — один.
Золотое правило: Если сомневаетесь — начинайте с интерфейса. Класс можно добавить позже, когда понадобится общая реализация. Начинающим часто кажется, что extends "проще", но в долгосрочной перспективе implements даёт больше гибкости.