Перейти к основному содержимому

Маркерные интерфейсы в Java

· 5 мин. чтения

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 {
}

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

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.

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

Как всегда, код можно найти на GitHub .