«1. Введение

В этом кратком руководстве мы узнаем об интерфейсах маркеров в Java.

2. Маркерные интерфейсы

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

Интерфейс маркера также называется интерфейсом тегирования.

Хотя маркерные интерфейсы все еще используются, они, скорее всего, указывают на запах кода и должны использоваться осторожно. Основная причина этого в том, что они стирают границы того, что представляет собой интерфейс, поскольку маркеры не определяют никакого поведения. В более новых разработках предпочтение отдается аннотациям для решения некоторых из тех же проблем.

3. Интерфейсы маркеров JDK

Java имеет множество встроенных интерфейсов маркеров, таких как Serializable, Cloneable и Remote.

Возьмем пример интерфейса Cloneable. Если мы попытаемся клонировать объект, который не реализует этот интерфейс, JVM выдаст исключение CloneNotSupportedException. Следовательно, интерфейс маркера Cloneable является индикатором для JVM, что мы можем вызвать метод Object.clone().

Таким же образом при вызове метода ObjectOutputStream.writeObject() JVM проверяет, реализует ли объект интерфейс маркера Serializable. Если это не так, создается исключение NotSerializableException. Поэтому объект не сериализуется в выходной поток.

4. Пользовательский интерфейс маркера

Давайте создадим наш собственный интерфейс маркера.

Например, мы могли бы создать маркер, указывающий, можно ли удалить объект из базы данных:

public interface Deletable {
}

Чтобы удалить объект из базы данных, объект, представляющий этот объект, должен реализовать наш маркер удаления interface:

public class Entity implements Deletable {
    // implementation details
}

Допустим, у нас есть объект DAO с методом удаления сущностей из базы данных. Мы можем написать наш метод delete() так, чтобы могли быть удалены только объекты, реализующие наш интерфейс маркера:

public class ShapeDao {

    // other dao methods

    public boolean delete(Object object) {
        if (!(object instanceof Deletable)) {
            return false;
        }

        // delete implementation details
        
        return true;
    }
}

Как мы видим, мы даем указание JVM о поведении наших объектов во время выполнения. Если объект реализует наш интерфейс маркера, его можно удалить из базы данных.

5. Маркерные интерфейсы и аннотации

Внедрив аннотации, Java предоставил нам альтернативу для достижения тех же результатов, что и маркерные интерфейсы. Более того, подобно интерфейсам маркеров, мы можем применять аннотации к любому классу и использовать их как индикаторы для выполнения определенных действий.

Так в чем ключевое отличие?

В отличие от аннотаций, интерфейсы позволяют нам использовать преимущества полиморфизма. В результате мы можем добавить дополнительные ограничения в интерфейс маркера.

Например, добавим ограничение на удаление из базы данных только типа Shape:

public interface Shape {
    double getArea();
    double getCircumference();
}

В этом случае интерфейс нашего маркера, назовем его DeletableShape, будет выглядеть следующим образом:

public interface DeletableShape extends Shape {
}

Тогда наш класс будет реализовывать интерфейс маркера:

public class Rectangle implements DeletableShape {
    // implementation details
}

Следовательно, все реализации DeletableShape также являются реализациями Shape. Очевидно, что мы не можем сделать это с помощью аннотаций.

Тем не менее, каждое дизайнерское решение имеет компромиссы, и полиморфизм можно использовать в качестве контраргумента против маркерных интерфейсов. В нашем примере каждый класс, расширяющий Rectangle, автоматически реализует DeletableShape.

6. Маркерные интерфейсы в сравнении с типичными интерфейсами

В предыдущем примере мы могли бы получить те же результаты, изменив метод delete() нашего DAO, чтобы проверить, является ли наш объект Shape или нет, вместо того, чтобы проверять, является ли он формой. Удаляемый:

public class ShapeDao { 

    // other dao methods 
    
    public boolean delete(Object object) {
        if (!(object instanceof Shape)) {
            return false;
        }
    
        // delete implementation details
        
        return true;
    }
}

Так зачем создавать маркерный интерфейс, если мы можем добиться тех же результатов, используя обычный интерфейс?

Давайте представим, что помимо типа Shape мы хотим удалить из базы данных еще и тип Person. В этом случае есть два варианта добиться этого:

Первый вариант — добавить дополнительную проверку к нашему предыдущему методу delete(), чтобы проверить, является ли удаляемый объект экземпляром Person или нет.

public boolean delete(Object object) {
    if (!(object instanceof Shape || object instanceof Person)) {
        return false;
    }
    
    // delete implementation details
        
    return true;
}

«

«Но что, если у нас есть еще типы, которые мы также хотим удалить из базы данных? Очевидно, что это не будет хорошим вариантом, потому что нам придется менять наш метод для каждого нового типа.

Второй вариант — сделать так, чтобы тип Person реализовывал интерфейс Shape, который действует как интерфейс маркера. Но является ли объект Person формой? Ответ однозначно нет, и это делает второй вариант хуже первого.

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

7. Заключение

В этой статье мы обсудили, что такое маркерные интерфейсы и как их можно использовать. Затем мы рассмотрели некоторые встроенные в Java примеры интерфейсов этого типа и то, как они используются JDK.

Затем мы создали собственный интерфейс маркера и сравнили его с использованием аннотации. Наконец, мы заканчиваем тем, что видим, почему в некоторых сценариях рекомендуется использовать интерфейс маркера вместо традиционного интерфейса.