Javanese Online

Интерфейс-маркер

Интерфейс — это инструмент для объявления контракта. Маркер-интерфейс — это интерфейс, который не объявляет контракт, а используется для других целей.

Примеры

Serializable

Разрешает записывать объекты этого класса в ObjectOutputStream.

Проблема: нет никакой гарантии, что это произойдёт успешно, т. к. объект может содержать не-Serializable поля.

Нормальное решение: убрать маркер-интерфейс, позволить записывать таким способом любые объекты. Всё равно хуже уже не будет.

Cloneable

Меняет поведение метода Object#clone(). Для не-Cloneable он бросает исключение, для Cloneable — возвращает shallow copy объекта.

Проблема: не объявляет контракт для клонирования, а изменяет поведение метода суперкласса. Имеет смысл только для изменяемых объектов.

Решение: для изменяемых объектов — copy constructor, для неизменяемых — подобие метода copy из data-классов в Kotlin.

Type

Class из Java 1.0 обозначает классы и интерфейсы, примитивные типы и массивы.

GenericArrayType, ParameterizedType, TypeVariable, WildcardType обозначают generic-типы из Java 1.5.

Все они реализуют интерфейс Type из Java 1.5, который их и собирает воедино.

Проблема: нет нормального способа узнать, какой конкретно перед нами тип. instanceof — не нормальный способ, так как нет механизма языка, который мог бы гарантировать, что все возможные варианты рассмотрены.

Решение: использовать полиморфизм для ветвления. Например, объявить интерфейс Type так:

public interface Type {
    
    <R> R visit(Visitor<R> visitor);

    interface Visitor<R> {
        R isClass(Class<?> type);
        R isGenericArray(GenericArrayType type);
        R isParameterizedType(ParameterizedType type);
        R isTypeVariable(TypeVariable<?> type);
        R isWildcardType(WildcardType type);
    }

}

Каждая реализация должна звать соответствующий метод.

public class Class<T> implements ..., Type, ... {

    // тело класса пропущено...

    @Override public <R> R visit(Visitor<R> visitor) {
        return visitor.isClass(this);
    }

}

public class RealGenericArrayType implements GenericArrayType {

    // тело класса пропущено...

    @Override public <R> R visit(Visitor<R> visitor) {
        return visitor.isGenericArray(this);
    }

}

// и так далее

Тогда можно будет инспектировать тип просто и однозначно (но, конечно, многословно):

String typeDescription = someType.visit(new Type.Visitor<String>() {
    @Override public String isClass(Class<?> type) {
        return "normal class";
    }
    @Override public String isGenericArray(GenericArrayType type) {
        return "generic array, LOL";
    }
    @Override public String isParameterizedType(ParameterizedType type) {
        return "parameterized type";
    }
    @Override public String isTypeVariable(TypeVariable<?> type) {
        return "type variable ¯\\_(ツ)_/¯";
    }
    @Override public String isWildcardType(WildcardType type) {
        return "wildcard";
    }
});
System.out.println("Given type is a " + typeDescription);

Если по необъяснимым причинам возникла необходимость пометить класс каким-то маркером, то аннотации подходят для этого лучше.

Массивы

Массивы — жертвы интерфейсов-маркеров: они реализуют Cloneable и Serializable, но виртуальных методов у них нет: vtable и itable технически отсутствуют.

Комментарии к уроку

Сообщить об ошибке

Javanese.Online в GitHub

Чаты и каналы в Telegram

RSS-лента