Интерфейсы маркеров в Java?

Меня учили, что интерфейс Marker в Java является пустым интерфейсом и используется для передачи сигнала компилятору или JVM о том, что объекты класса, реализующие этот интерфейс, должны обрабатываться особым образом, например, сериализация, клонирование и т. Д.

Но в последнее время я узнал, что это на самом деле не имеет ничего общего с компилятором или JVM. Например, в случае Serializable интерфейс метод writeObject(Object) из ObjectOutputStream делает что-то вроде instanceOf Serializable определить, реализует ли класс Serializable и броски NotSerializableException соответственно. Все обрабатывается в коде, и это, кажется, шаблон дизайна, поэтому я думаю, что мы можем определить наши собственные интерфейсы маркеров.

Теперь мои сомнения:

  1. Является ли определение интерфейса маркера, упомянутое выше в 1-й точке, неправильным? Как мы можем определить интерфейс Маркер тогда?

  2. И вместо того, чтобы использовать instanceOf оператор почему метод не может быть что-то вроде writeObject(Serializable) так что есть проверка типов во время компиляции, а не во время выполнения?

  3. Чем аннотации лучше, чем интерфейс маркеров?

10 ответов

Решение
  1. Является ли определение интерфейса маркера, упомянутое выше в 1-й точке, неправильным? - В частях правильно, что (1) интерфейс маркера должен быть пустым, и (2) его реализация подразумевает некоторую особую обработку класса реализации. Часть, которая является неправильной, состоит в том, что это подразумевает, что JVM или компилятор будут обращаться с объектами этого класса по-разному: вы правы, заметив, что это код библиотеки классов Java, который обрабатывает эти объекты как клонируемые, сериализуемые и т. Д. ничего общего с компилятором или JVM.
  2. вместо использования оператора instanceOf, почему метод не может быть чем-то вроде writeObject(Serializable) так что есть проверка типа во время компиляции - это позволяет избежать загрязнения вашего кода с именем интерфейса маркера, когда "обычный" Object "необходимо. Например, если вы создаете класс, который должен быть сериализуемым и имеет члены-объекты, вам придется либо выполнять приведение, либо создавать свои объекты Serializable во время компиляции. Это неудобно, потому что интерфейс лишен какой-либо функциональности.
  3. Чем аннотации лучше маркерных интерфейсов? - Они позволяют вам достичь той же цели - передавать метаданные о классе его потребителям, не создавая отдельный тип для него. Аннотации также более мощные, позволяя программистам передавать более сложную информацию классам, которые ее "потребляют".

Невозможно провести в жизнь Serializable на writeObject потому что потомки не сериализуемого класса могут быть сериализуемыми, но их экземпляры могут быть выгружены обратно в родительский класс. В результате, удерживая ссылку на что-то не сериализуемое (например, Object) не означает, что указанный экземпляр действительно не может быть сериализован. Например, в

   Object x = "abc";
   if (x instanceof Serializable) {
   }

родительский класс (Object) не сериализуем и будет инициализирован с использованием конструктора без параметров. Значение, на которое ссылается x, String, является сериализуемым и условный оператор будет выполняться.

Я сделал простую демонстрацию, чтобы разрешить сомнения № 1 и 2:

У нас будет интерфейс Movable, который будет реализован классом MobilePhone.java, и еще один класс LandlinePhone.java, который НЕ реализует интерфейс Movable.

Наш маркер интерфейс:

package com;

public interface Movable {

}

LandLinePhone.java и MobilePhone.java

 package com;

 class LandLinePhone {
    // more code here
 }
 class MobilePhone implements Movable {
    // more code here
 }

Наш класс исключений: пакет com;

открытый класс NotMovableException расширяет Exception {

private static final long serialVersionUID = 1L;

@Override
public String getMessage() {
    return "this object is not movable";
}
// more code here
}

Наш тестовый класс: TestMArkerInterface.java

package com;

 public class TestMarkerInterface {

public static void main(String[] args) throws NotMovableException {
    MobilePhone mobilePhone = new MobilePhone();
    LandLinePhone landLinePhone = new LandLinePhone();

    TestMarkerInterface.goTravel(mobilePhone);
    TestMarkerInterface.goTravel(landLinePhone);

}

public static void goTravel(Object o) throws NotMovableException {
    if (!(o instanceof Movable)) {
        System.out.println("you cannot use :" + o.getClass().getName() + "   while travelling");
        throw new NotMovableException();
    }

    System.out.println("you can use :" + o.getClass().getName() + "   while travelling");
}}

Теперь, когда мы выполняем основной класс:

you can use :com.MobilePhone while travelling
you cannot use :com.LandLinePhone while travelling
Exception in thread "main" com.NotMovableException: this object is not movable
    at com.TestMarkerInterface.goTravel(TestMarkerInterface.java:22)
    at com.TestMarkerInterface.main(TestMarkerInterface.java:14)

Так какой класс реализует маркерный интерфейс, Movable пройдет тест, иначе появится сообщение об ошибке.

Это способ проверки оператора instanceOf для Serializable, Cloneable и т. Д.

Маркерный интерфейс в Java - это интерфейс без полей или методов. Проще говоря, пустой интерфейс в Java называется интерфейсом маркера. Примерами интерфейсов маркеров являются Serializable, Cloneable а также Remote интерфейсы. Они используются для указания некоторой информации для компиляторов или JVM. Так что, если JVM видит, что класс Serializable, он может сделать некоторые специальные операции на нем. Точно так же, если JVM видит, что некоторый класс реализует Cloneable, он может выполнять некоторые операции для поддержки клонирования. То же самое верно для RMI и Remote интерфейс. Короче говоря, интерфейс маркера указывает сигнал или команду компилятору или JVM.

Вышесказанное началось как копия поста в блоге, но было слегка отредактировано для грамматики.

Интерфейс маркера / A, как следует из его названия, существует только для того, чтобы уведомить все, что о нем известно, о том, что класс что-то объявляет. Все что угодно может быть классами JDK для Serializable интерфейс или любой другой класс, который вы пишете для своего собственного.

b / Если это маркерный интерфейс, это не должно подразумевать существование какого-либо метода - было бы лучше включить подразумеваемый метод в интерфейс. Но вы можете решить, как это сделать, если хотите знать, зачем вам это нужно

c/ Существует небольшая разница между пустым интерфейсом и аннотацией, в которой нет значения или параметра. Но разница есть: аннотация может объявлять список ключей / значений, которые будут доступны во время выполнения.

  1. Он не имеет никакого отношения (обязательно) к JVM и компиляторам, он имеет отношение к любому коду, который заинтересован и тестирует данный интерфейс маркера.

  2. Это дизайнерское решение, и оно сделано по уважительной причине. Смотрите ответ от Аудрюса Мешкаускаса.

  3. Что касается этой конкретной темы, я не думаю, что это вопрос быть лучше или хуже. Интерфейс маркера делает то, что должен делать просто отлично.

А. Я всегда рассматривал их как шаблон дизайна, и ничего особенного JVM Я использовал этот шаблон в нескольких ситуациях.

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

Аннотации предназначены для предоставления метаинформации в код, и я думаю, что маркер - это метаинформация. Так что они как раз для этого варианта использования.

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

public interface MarkerEntity {

}

public boolean save(Object object) throws InvalidEntityFoundException {
   if(!(object instanceof MarkerEntity)) {
       throw new InvalidEntityFoundException("Invalid Entity Found, can't be  saved);
   } 
   return db.save(object);
}

Здесь метод save гарантирует, что сохраняются только объекты классов, которые реализуют интерфейс MarkerEntity, для других типов выбрасывается InvalidEntityFoundException. Итак, здесь интерфейс маркера MarkerEntity определяет тип, который добавляет специальное поведение к классам, реализующим его.

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

Но аннотации маркеров не могут полностью заменить интерфейсы маркеров, потому что; интерфейсы маркера используются для определения типа (как уже объяснено выше), а аннотации маркера - нет.

Источник для комментария интерфейса маркера

Прежде всего, я бы сказал, что Serializable и Cloneable являются плохими примерами маркерных интерфейсов. Конечно, они являются интерфейсами с методами, но они подразумевают такие методы, как writeObject(ObjectOutputStream), (Компилятор создаст writeObject(ObjectOutputStream) метод для вас, если вы не переопределите его, и все объекты уже имеют clone(), но компилятор снова создаст настоящий clone() Метод для вас, но с оговорками. Оба из них - странные крайние случаи, которые действительно не являются хорошими примерами дизайна.)

Маркерные интерфейсы обычно используются для одной из двух целей:

1) В качестве ярлыка, чтобы избежать чрезмерно длинного типа, что может случиться с большим количеством дженериков. Например, скажем, у вас есть подпись этого метода:

public void doSomething(Foobar<String, Map<String, SomethingElse<Integer, Long>>>) { ... }

Это грязно и надоедливо набирать, а главное, трудно понять. Рассмотрим это вместо этого:

public interface Widget extends Foobar<String, Map<String, SomethingElse<Integer, Long>>> { }

Тогда ваш метод выглядит так:

public void doSomething(Widget widget) { ... }

Мало того, что это стало понятнее, теперь вы можете Javadoc интерфейс Widget, а также легче искать все вхождения в вашем коде Widget.

2) Маркерные интерфейсы также можно использовать как способ обойти отсутствие в Java типов пересечений. С интерфейсом маркера вы можете требовать, чтобы что-то было двух разных типов, например, в сигнатуре метода. Скажем, у вас есть интерфейсный виджет в вашем приложении, как мы описали выше. Если у вас есть метод, который требует Widget, который также позволяет вам перебирать его (он придуман, но работайте со мной здесь), ваше единственное хорошее решение - создать интерфейс маркера, который расширяет оба интерфейса:

public interface IterableWidget extends Iterable<String>, Widget { }

И в вашем коде:

public void doSomething(IterableWidget widget) {
    for (String s : widget) { ... }
}

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

Другие вопросы по тегам