Что такое default-методы в интерфейсах Java и какие проблемы они решают?

«Что такое default-методы в интерфейсах Java и какие проблемы они решают?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Default-методы (методы по умолчанию) — это методы в интерфейсе Java, имеющие реализацию. Они были введены в Java 8 с ключевым словом default.

Основная решаемая проблема: Эволюция интерфейсов без нарушения обратной совместимости. До Java 8 добавление любого нового метода в интерфейс ломало все существующие классы, его реализующие, так как требовало немедленной реализации. Default-методы позволяют добавлять новую функциональность в интерфейсы, предоставляя готовую реализацию "по умолчанию", которую классы могут унаследовать или переопределить.

Синтаксис и пример:

interface Vehicle {
    // Абстрактный метод (обязателен для реализации)
    void start();

    // Default-метод (уже имеет реализацию)
    default void honk() {
        System.out.println("Beep beep from the interface!");
    }

    // Еще один default-метод (помощник)
    default String getFuelType() {
        return "Generic fuel";
    }
}

class Car implements Vehicle {
    @Override
    public void start() {
        System.out.println("Car engine started.");
    }
    // Метод honk() НЕ переопределен, будет использована default-реализация.
}

// Использование
Vehicle myCar = new Car();
myCar.start(); // Вывод: Car engine started.
myCar.honk();  // Вывод: Beep beep from the interface!

Ключевые правила и особенности:

  1. Переопределение: Класс, реализующий интерфейс, может переопределить default-метод, изменив его поведение.
  2. Конфликт множественного наследования: Если класс реализует два интерфейса с одинаковыми default-методами, возникает неоднозначность. Компилятор требует явного переопределения этого метода в классе, где можно вызвать нужную реализацию через InterfaceName.super.methodName().
    interface A { default void foo() { System.out.println("A"); } }
    interface B { default void foo() { System.out.println("B"); } }
    class C implements A, B {
        @Override
        public void foo() {
            A.super.foo(); // Явно выбираем реализацию из A
        }
    }
  3. Нет доступа к состоянию: Default-методы не имеют доступа к приватным полям класса, который их реализует. Они могут работать только с данными, доступными через другие методы интерфейса.
  4. Не могут переопределять методы Object: Интерфейс не может объявить default-реализацию для equals, hashCode или toString — это вызовет ошибку компиляции.