Для какой цели в Java 8 были добавлены default-методы в интерфейсах?

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

Ответ

Основная цель: Обеспечение обратной совместимости при эволюции API, в частности, для библиотек коллекций (Collections Framework) и Stream API.

Проблема до Java 8: Интерфейс — это чистый контракт. Добавление нового метода в существующий интерфейс ломало все классы, которые его реализовывали, так как требовало немедленной реализации этого метода.

Решение в Java 8: default-методы (методы с реализацией по умолчанию) позволяют добавлять новую функциональность в интерфейсы, не нарушая работу существующего кода.

Классический пример — метод stream() в интерфейсе Collection:

public interface Collection<E> extends Iterable<E> {
    // ... другие методы

    // Новый метод, добавленный в Java 8. Все существующие реализации
    // (ArrayList, HashSet и т.д.) автоматически его "получили".
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
}

// Старый код, написанный до Java 8, продолжает работать.
// Новый код может использовать stream() на любой коллекции.
List<String> list = Arrays.asList("a", "b", "c");
list.stream().forEach(System.out::println); // Работает!

Другие важные аспекты:

  1. Множественное наследование поведения: Класс может реализовывать несколько интерфейсов с default-методами, что является формой множественного наследования в Java.
  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(); // Явно указываем, какую реализацию использовать
        }
    }
  3. Отличие от абстрактных классов: default-методы не могут переопределять методы Object (например, toString, equals) и не могут содержать состояние (поля). Их задача — предоставлять поведение по умолчанию.