Avatar ·

Implements и extends в Java: когда использовать и в чем разница?

Всем привет! Я начал изучать Java и никак не могу понять разницу между ключевыми словами extends и implements. Вроде бы оба используются для наследования, но когда что применять? Я написал небольшой код, но он не компилируется и я чувствую, что путаю концепции: ```java class Animal { void eat() { System.out.println("Ест"); } } interface Pet { void play(); } // Вот тут я не уверен class Dog extends Animal implements Pet { // пытаюсь переопределить метод из интерфейса void play() { System.out.println("Играет с мячом"); } } public class Main { public static void main(String[] args) { Dog dog = new Dog(); dog.eat(); dog.play(); } } ``` Ошибка: метод play() в Dog не переопределяет метод из Pet, потому что он не public. Я исправил на public, но все равно не до конца понимаю: 1. Почему для класса Animal я использовал extends, а для Pet — implements? 2. Можно ли сделать наоборот: implements Animal и extends Pet? 3. Когда какой вариант выбирать в реальных проектах? Объясните, пожалуйста, на пальцах, с примерами. Заранее спасибо! <<>>

Ключевая разница: класс 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), но сигнатура всё равно должна совпадать.

Ответы на ваши вопросы

  1. Почему для Animal extends, а для Pet implements? Потому что Animal — это класс с реализацией, а Pet — интерфейс без реализации. Вы не можете "расширить" интерфейс через extends (хотя интерфейсы могут расширять другие интерфейсы через extends).
  2. Можно ли implements Animal и extends Pet? Нет. implements работает только с интерфейсами, extends — с классами (или интерфейсами, если речь о наследовании интерфейсов). Попытка написать class Cat implements Animal вызовет ошибку компиляции, так как Animal — это класс.
  3. Когда что выбирать? Используйте 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 даёт больше гибкости.