Чем в современных Java-приложениях заменяют интерфейсы-маркеры?

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

Ответ

Интерфейсы-маркеры (пустые интерфейсы, например, Serializable, Cloneable) устарели как концепция. Современные альтернативы более выразительны и функциональны:

1. Аннотации (Предпочтительный способ)

Аннотации несут семантическую информацию (метаданные) и могут иметь параметры.

// Вместо интерфейса-маркера Persistable
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Persistable {
    String storage() default "default";
}

@Persistable(storage = "redis")
public class UserEntity { /* ... */ }

2. Интерфейсы с default-методами (начиная с Java 8)

Позволяют добавить поведение по умолчанию, сохраняя обратную совместимость.

// Вместо маркера Loggable
public interface Loggable {
    // Метод по умолчанию, а не пустой интерфейс
    default void log(String message) {
        System.out.println("[" + getClass().getSimpleName() + "] " + message);
    }
}

3. Функциональные интерфейсы (@FunctionalInterface)

Для обозначения интерфейсов с одним абстрактным методом (SAM). Аннотация @FunctionalInterface делает намерение компилятору и разработчику явным.

@FunctionalInterface // Явное указание назначения интерфейса
public interface Validator<T> {
    boolean validate(T t);
    // Можно добавлять default-методы
    default Validator<T> and(Validator<T> other) {
        return t -> validate(t) && other.validate(t);
    }
}

4. Специализированные интерфейсы из java.util.function

Готовые функциональные интерфейсы для общих сценариев (Predicate, Function, Consumer).

Вывод: Аннотации и интерфейсы с поведением (default-методы) предоставляют более богатые и типобезопасные возможности для выражения намерений, чем пустые интерфейсы-маркеры.