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


134

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

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

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

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

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

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


8
Serializableв качестве аннотации это нонсенс, а @NonNullв качестве интерфейса - нонсенс. Я бы сказал: аннотации - это маркеры + метаданные. Кстати: предвестником аннотаций был XDoclet, родившийся в Javadoc, убитый аннотациями.
Мрачный

Ответы:


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

14
Я всегда понимал, что аннотации - это своего рода «интерфейс
маркеров

и потому что writeObjectявляется частным, это означает, что, например, класс не должен вызывать реализацию суперкласса
ratchet freak

15
Следует иметь в виду, что аннотации были добавлены в язык спустя несколько лет после первоначальной разработки стандартной библиотеки. Если бы аннотации были на языке с самого начала, сомнительно, что Serializable был бы интерфейсом, вероятно, это была бы аннотация.
Теодор Норвелл

Что ж, в случае Cloneableнеясно, будет ли изменена обработка экземпляра в библиотеке или JVM.
Хольгер

«[...] без создания отдельного типа для него.» - Я бы сказал , что это именно то , что отличает их: Интерфейс маркеров делает ввести тип, в то время как аннотации (вид) does't.
августа

22

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

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

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


6

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

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


6
Копировать можно, но укажите также источник: javarevisited.blogspot.com/2012/01/… . Также будет хорошо, если вы не скопируете и вставите орфографические ошибки. :)
Саураб Патил

6

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

У нас будет интерфейс Movable, который будет реализован MobilePhone.javaClass, и еще один класс, 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;

public class NotMovableException extends 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 и т. Д.


5

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

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

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


3

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

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

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


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

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

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


Можете ли вы добавить ссылку на «ответ от Audrius Meškauskas»? Я не вижу ничего на этой странице с таким именем.
Сара Мессер

2

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

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 определяет тип, который добавляет специальное поведение к классам, реализующим его.

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

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

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


1
+1 за то, что он фактически используется как простой «безопасный» хук, который программист должен явно сказать, что его можно сохранить в качестве примера в базе данных.
Людвиг Вт

1

Прежде всего, я бы сказал, что 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) { ... }
}

1
Компилятор не создает никаких методов, если ваш класс реализует Serializableили Cloneable. Вы можете проверить это, проверив ваши файлы классов. Кроме того, создание «горячих интерфейсов» - это не то, чем занимаются маркерные интерфейсы. И это действительно плохая практика кодирования, так как она требует создания дополнительных классов реализации для выполнения дополнительных интерфейсов. Кстати, Java имеет типы пересечений уже десять лет. Узнайте о дженериках ...
Хольгер

Для клонируемых вы правы. Это меняет то, что делает Object.clone (). Это все еще ужасно. См. Ссылку Джоша Блоха. Интерфейсы быстрого доступа не обязательно являются хорошим шаблоном проектирования, поскольку вы произвольно ограничиваете то, что можете отправить в метод. В то же время я чувствую, что написание более ясного, хотя и более ограничительного кода, обычно является разумным компромиссом. Что касается типов пересечений, это относится только к обобщенным типам, и они не являются денотируемыми и, следовательно, бесполезны во многих случаях. Попробуйте иметь переменную экземпляра, которая является Serializable и, ну, что-нибудь еще.
MikeyB

0

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.