Маркерні інтерфейси на Java?


134

Мене вчили, що інтерфейс Marker на Java - це порожній інтерфейс і використовується для передачі сигналу компілятору або JVM, що до об'єктів класу, що реалізує цей інтерфейс, слід поводитися особливим чином, як серіалізація, клонування тощо.

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

Тепер мої сумніви:

  1. Чи неправильне визначення інтерфейсу маркера, згаданого вище, у першій точці? Як тоді ми можемо визначити інтерфейс Marker?

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

  3. Чим анотації краще інтерфейсів маркера?


8
Serializableяк Анотація - це нісенітниця, а @NonNullяк Інтерфейс - нісенітниця. Я б сказав: Анотації - це маркери + метадані. BTW: Передвісником анотацій став XDoclet, який народився в Джавадоку, вбитий Анотаціями.
Грим

Відповіді:


117
  1. Чи неправильне визначення інтерфейсу маркера, згаданого вище, у першій точці? - В частинах правильно, що (1) інтерфейс маркера повинен бути порожнім, а (2) реалізація цього має на увазі певну особливу обробку класу реалізації. Неправильна частина полягає в тому, що це означає, що JVM або компілятор буде по-різному ставитися до об'єктів цього класу: ви правильні, спостерігаючи, що саме код бібліотеки класів Java трактує ці об'єкти як клоновані, серіалізаційні тощо. нічого спільного з компілятором чи JVM.
  2. замість того, щоб використовувати оператор instanceOf, чому метод не може бути чимось подібним, writeObject(Serializable)щоб відбулася перевірка типу компіляції - Це дозволяє уникнути забруднення вашого коду ім'ям інтерфейсу маркера, коли потрібен "звичайний Object". Наприклад, якщо ви створюєте клас, який повинен бути серіалізаційним і має учасників об’єкта, ви будете змушені або робити кастинг, або робити об’єкти Serializableпід час компіляції. Це незручно, оскільки інтерфейс позбавлений будь-якої функціональності.
  3. Наскільки анотації краще, ніж інтерфейси маркера? - Вони дозволяють досягти тієї самої мети, щоб передати метадані про клас його споживачам, не створюючи для нього окремого типу. Анотації теж потужніші, вони дозволяють програмістам передавати більш досконалу інформацію класам, які "споживають її".

14
Я завжди розумів, що анотації - це свого роду "Маркерні інтерфейси 2.0": Серіалізація існує з Java 1.1, Анотації додані в 1.5
blagae

а тому writeObject, що приватний, це означає, що, наприклад, клас не повинен викликати реалізацію надкласового рівня
ratchet freak

15
Слід пам’ятати, що анотації додаються до мови через кілька років після того, як стандартна бібліотека була створена спочатку. Якби анотації були в мові з самого початку, сумнівно, що Serializable був би інтерфейсом, це, мабуть, було б анотацією.
Теодор Норвелл

Що ж, у випадку з Cloneableцим не зрозуміло, чи це змінена бібліотека чи JVM-обробка екземпляра.
Холгер

"[...], не створюючи для цього окремого типу." - Я б сказав , що це саме те , що відрізняє їх: Інтерфейс маркерів робить ввести тип, в той час як анотації (вид) does't.
aioobe

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:

У нас буде мобільний інтерфейс, який буде реалізований MobilePhone.javaкласом та ще одним класом, LandlinePhone.javaякі НЕ реалізують рухомий інтерфейс

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

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інтерфейсу, або будь-який клас, який ви пишете для власного класу.

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

c / Між порожнім інтерфейсом та приміткою, яка не використовує значення та параметр, є невелика різниця. Але різниця є: в анотації можна оголосити список ключів / значень, які будуть доступні під час виконання.


3

а. Я завжди розглядав їх як модель дизайну, і ніщо JVM-Special я не використовував цю схему в декількох ситуаціях.

c. Я вважаю, що використання анотацій для позначення чогось є кращим рішенням, ніж використання інтерфейсів маркера. Просто тому, що інтерфейси в першу чергу спрямовані на визначення загальних інтерфейсів типів / класів. Вони є частиною класу-ієрахії.

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


3
  1. Це не має нічого спільного з JVM та компіляторами, він має щось спільне з будь-яким кодом, який цікавить і тестує на даний інтерфейс маркера.

  2. Це дизайнерське рішення, і воно робиться з поважної причини. Дивіться відповідь Аудрія Мешкаускаса.

  3. Щодо цієї конкретної теми, я не думаю, що справа в тому, щоб бути кращим чи гіршим. Інтерфейс маркера робить те, що він повинен робити просто чудово.


Чи можете ви додати посилання на "відповідь Одріуса Мешкаускаса"? На цій сторінці я нічого не бачу з цим ім'ям.
Сара Мессер

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);
}

Тут метод збереження гарантує збереження лише об’єктів класів, що реалізують інтерфейс MarkerEntity, для інших типів викидається InvalidEntityFoundException. Тому тут інтерфейс маркера MarkerEntity визначає тип, який додає класам особливої ​​поведінки.

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

Але анотації маркера не можуть повністю замінити інтерфейси маркера, оскільки; Маркерні інтерфейси використовуються для визначення типу (як уже було пояснено вище), де як анотації маркера цього немає.

Джерело для коментаря інтерфейсу маркера


1
+1 за те, що він показує, що його фактично використовують як просту гачку "безпеки", яку програміст повинен прямо сказати, що його можна зберегти, наприклад, до бази даних.
Ludvig W

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 інтерфейс віджетів, а також простіше шукати всі події у коді віджету.

2) Маркерні інтерфейси також можуть бути використані як спосіб усунення відсутності типів перетину Java. За допомогою інтерфейсу маркера ви можете вимагати, щоб щось було двох різних типів, наприклад, у підписі методу. Скажімо, у вашому додатку є якийсь віджет інтерфейсу, як ми описали вище. Якщо у вас є метод, який вимагає віджета, який також дозволяє вам повторити його (це надумано, але працюйте зі мною тут), ваше єдине хороше рішення - створити інтерфейс маркера, який розширює обидва інтерфейси:

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

І у вашому коді:

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

1
Компілятор не створює жодних методів, якщо ваш клас реалізує Serializableабо Cloneable. Ви можете перевірити це, перевіривши файли класу. Крім того, створення "інтерфейсів швидкого доступу" - це не те, що стосується маркерних інтерфейсів. І це справді погана практика кодування, оскільки це вимагатиме створення додаткових класів реалізації для виконання додаткових інтерфейсів. До речі, Java вже десять років має типи перетину. Дізнайтеся про Generics…
Holger

Що стосується клонування, ви маєте рацію. Це змінює те, що робить Object.clone (). Це все ще жахливо. Див Josh Блоха посилання Поєднання інтерфейси не обов'язково хороший шаблон дизайну, як ви довільно обмежувати то , що ви можете відправити методу. У той же час я відчуваю, що писати чіткіше, якщо трохи більш обмежувальний код, як правило, є розумним компромісом. Що стосується типів перетину, то це стосується лише дженериків, які не позначаються, а тому багато разів корисні. Спробуйте встановити змінну примірника, яка є Serializable і, ну, і все інше.
MikeyB

0

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.