Javanese Online

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

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

Примеры

Serializable

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

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

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

Cloneable

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

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

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

Type

В интерфейсе нет осмысленных методов (в Java 1.8 добавлен первый метод — getTypeName), это маркер, которым помечены типы, описывающие разновидности типов в Java: Class из Java 1.0 описывает классы и интерфейсы, примитивные типы и массивы; GenericArrayType, ParameterizedType, TypeVariable, WildcardType описывают generic-типы из Java 1.5.

Этот интерфейс — неудачная попытка изобразить тип-сумму, который в Java не поддерживается явным образом.

На самом деле, такие типы допустимы в multi-catch из Java 1.7 в очень ограниченном виде: catch (SomeException|SomeOtherException e)

Тип-сумма также называется следующими терминами: алгебраический тип, меченое объединение, дизъюнктивное объединение, tagged union, variant record, choice type, discriminated union, disjoint union, sum type. Схожие по смыслу понятия: sealed class, union-тип.

Проблема: нет надёжного способа узнать, какой конкретно перед нами тип. 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);

Эта техника также называется double dispatch, т. к. во время выполнения происходит два виртуальных вызова, один за другим.

Другое

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

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

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

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

Javanese.Online в GitHub

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

RSS-лента