Что такое модификатор default для методов в интерфейсах Java?

Ответ

Модификатор default (метод по умолчанию) позволяет добавлять в интерфейсы Java методы с реализацией, не ломая существующие классы, которые этот интерфейс уже реализуют. Введены в Java 8 для эволюции API.

Основная цель: Обеспечить обратную совместимость при добавлении новой функциональности в интерфейсы.

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

interface Vehicle {
    // Обычный абстрактный метод
    String getBrand();

    // Метод по умолчанию - имеет реализацию
    default String startEngine() {
        return "Engine of " + getBrand() + " started.";
    }

    // Статический метод в интерфейсе (также с Java 8)
    static void honk() {
        System.out.println("Beep!");
    }
}

class Car implements Vehicle {
    @Override
    public String getBrand() {
        return "Toyota";
    }
    // Метод startEngine() НЕ ОБЯЗАТЕЛЬНО переопределять,
    // будет использована реализация по умолчанию.
}

// Использование
Car myCar = new Car();
System.out.println(myCar.startEngine()); // "Engine of Toyota started."
Vehicle.honk(); // Вызов статического метода

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

  1. Не обязателен для переопределения: Классы, реализующие интерфейс, могут использовать реализацию по умолчанию, не переопределяя метод.
  2. Конфликт при множественном наследовании: Если класс реализует два интерфейса с одинаковыми default-методами, возникает неоднозначность. Класс обязан явно переопределить этот метод.
    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. Не могут переопределять методы Object: Интерфейс не может объявить default-метод для equals, hashCode или toString.
  4. Модификаторы доступа: default-методы неявно public. Их нельзя сделать private, protected или static (для static есть отдельный вид методов).
  5. Отличие от абстрактных классов: Интерфейс с default-методами всё ещё не может иметь состояния (полей экземпляра), в отличие от абстрактного класса.

Типичное применение: Добавление удобных методов-утилит в интерфейсы коллекций (например, sort() в List<E>), что позволило улучшить API, не требуя изменений в миллионах строк существующего кода.

Ответ 18+ 🔞

Ну, слушай, вот есть у нас в Java интерфейсы, да? Классика жанра: нагородишь там кучу абстрактных методов, и потом все, кто их реализует, обязаны каждый из них прописать. А если через пять лет захотелось добавить в этот интерфейс ещё одну фичу — новую методяру? Так всех, кто его использует, придётся ломать и заставлять её реализовывать. Овердохуища работы, пиздец.

Вот чтобы такого не было, в Java 8 ввели, блядь, модификатор default. Гениальная, сука, идея! Теперь можно в интерфейс пихнуть метод с готовой реализацией, и все старые классы, которые этот интерфейс уже юзают, даже не чихнут — они просто получат этот метод в подарок, как есть. Обратная совместимость наше всё, ёпта!

Смотри, как это выглядит, на примере:

interface Vehicle {
    // Ну это обычный, старый добрый абстрактный метод. Без тела.
    String getBrand();

    // А ВОТ ЭТО УЖЕ НОВАЯ ФИШКА! Метод с реализацией прямо в интерфейсе.
    default String startEngine() {
        return "Engine of " + getBrand() + " started.";
    }

    // Заодно и статические методы в интерфейсы запихнули, кстати. Тоже с 8-й версии.
    static void honk() {
        System.out.println("Beep!");
    }
}

class Car implements Vehicle {
    @Override
    public String getBrand() {
        return "Toyota";
    }
    // И смотри, сука, в чём магия: метод startEngine() мне тут ПЕРЕОПРЕДЕЛЯТЬ НЕ НАДО!
    // Он уже работает, берёт мою getBrand() и подставляет. Красота!
}

// Ну и пользуемся:
Car myCar = new Car();
System.out.println(myCar.startEngine()); // Выведет: "Engine of Toyota started."
Vehicle.honk(); // А это статический метод, вызывается прямо от имени интерфейса.

Вот так вот, в рот меня чих-пых! Удобно же?

Но, как всегда, есть свои подводные, блядь, камни. Главный — конфликты при множественном наследовании. Представь, твой класс реализует два интерфейса, и в обоих есть default-метод с одинаковым именем. Компилятор сходит с ума: "Бля, а какую из двух реализаций-то брать?!" И он тебя заставит этот конфликт разрешить вручную.

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. Да, синтаксис тот ещё, ёперный театр.
    }
}

Ещё важные моменты, чтобы не облажаться:

  1. Нельзя трогать методы Object. Не вздумай объявить default boolean equals(Object o). Компилятор тебе такого не позволит, и правильно сделает.
  2. Все default-методы — public. Даже если не написать, они такие по умолчанию. Сделать их private или protected — нихуя.
  3. Это не абстрактный класс! Состояния (поля экземпляра) в интерфейс всё равно не запихнёшь. Только константы (public static final). Так что не путай.

Где это пригодилось? Да везде! Вот, например, в интерфейс List<E> добавили кучу удобных default-методов вроде sort() или replaceAll(). И все твои ArrayList, LinkedList и прочие коллекции моментально стали умнее, без единого изменения в их исходниках. Вот это и есть эволюция API без боли, блядь!

Короче, штука мощная. Главное — использовать с умом и не создавать лишних конфликтов, а то сам потом в них и запутаешься.