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 .