Interfejsy markerów w Javie?

134

Uczono mnie, że interfejs Marker w Javie jest pustym interfejsem i służy do sygnalizowania kompilatorowi lub JVM, że obiekty klasy implementującej ten interfejs muszą być traktowane w specjalny sposób, jak serializacja, klonowanie itp.

Ale ostatnio dowiedziałem się, że tak naprawdę nie ma to nic wspólnego z kompilatorem ani maszyną JVM. Na przykład, w przypadku Serializableinterfejsu metoda writeObject(Object)of ObjectOutputStreamrobi coś w rodzaju instanceOf Serializablewykrycia, czy klasa implementuje Serializable& generuje NotSerializableExceptionodpowiednio. Wszystko jest obsługiwane w kodzie i wydaje się, że jest to wzorzec projektowy, więc myślę, że możemy zdefiniować nasze własne interfejsy znaczników.

Teraz moje wątpliwości:

  1. Czy definicja interfejsu znacznika wspomniana powyżej w punkcie 1 jest błędna? Jak zatem możemy zdefiniować interfejs Markera?

  2. I zamiast używać instanceOfoperatora, dlaczego metoda nie może być podobna do writeObject(Serializable)sprawdzania typu w czasie kompilacji, a nie w czasie wykonywania?

  3. W jaki sposób adnotacje są lepsze niż interfejsy znaczników?

Xavier DSouza
źródło
8
Serializableponieważ adnotacja jest nonsensowna, a @NonNullinterfejs jest nonsensem. Powiedziałbym: adnotacje to znaczniki + metadane. BTW: Forefunner of Annotations był XDoclet, urodzony w Javadoc, zabity przez Annotations.
Ponury,

Odpowiedzi:

117
  1. Czy definicja interfejsu znacznika wspomniana powyżej w punkcie 1 jest błędna? - Prawdą jest w częściach, że (1) interfejs znacznika musi być pusty, a (2) jego implementacja ma oznaczać specjalne traktowanie klasy implementującej. Część, która jest niepoprawna, polega na tym, że sugeruje, że JVM lub kompilator inaczej traktowałby obiekty tej klasy: masz rację, zauważając, że to kod biblioteki klas Javy traktuje te obiekty jako klonowalne, serializowalne itp. nie ma nic wspólnego z kompilatorem ani maszyną JVM.
  2. zamiast używać operatora instanceOf, dlaczego metoda nie może być podobna do writeObject(Serializable)takiej, aby można było sprawdzić typ w czasie kompilacji - pozwala to uniknąć zanieczyszczania kodu nazwą interfejsu znacznika, gdy Objectpotrzebny jest „zwykły ”. Na przykład, jeśli utworzysz klasę, która musi być serializowalna i ma składowe obiektów, będziesz zmuszony albo wykonać rzutowanie, albo utworzyć obiekty Serializablew czasie kompilacji. Jest to niewygodne, ponieważ interfejs pozbawiony jest jakiejkolwiek funkcjonalności.
  3. Dlaczego adnotacje są lepsze niż interfejsy znaczników? - Pozwalają osiągnąć ten sam cel przekazywania metadanych o klasie konsumentom bez tworzenia dla niej osobnego typu. Adnotacje są również potężniejsze, pozwalając programistom przekazywać bardziej wyrafinowane informacje do klas, które je „konsumują”.
dasblinkenlight
źródło
14
Zawsze rozumiałem, że adnotacje są rodzajem „Marker Interfaces 2.0”: Serializable istnieje od wersji Java 1.1, Adnotacje zostały dodane w wersji 1.5
blagae
a ponieważ writeObjectjest prywatny, oznacza to, że na przykład klasa nie musi wywoływać implementacji superklasy
ratchet freak
15
Należy pamiętać, że adnotacje zostały dodane do języka kilka lat po pierwotnym zaprojektowaniu biblioteki standardowej. Gdyby adnotacje były w tym języku od początku, wątpliwe jest, czy Serializable byłby interfejsem, prawdopodobnie byłby to adnotacja.
Theodore Norvell
Cóż, w tym przypadku Cloneablenie jest jasne, czy jest to biblioteka, czy obsługa JVM instancji, która została zmieniona.
Holger,
„[…] bez tworzenia oddzielnego typu”. - Powiedziałbym, że to jest właśnie to, co odróżnia je: Interfejs marker ma wprowadzić typ, a adnotacje (niby) nie podlega.
aioobe
22

Nie można wymusić Serializablewłączenia, writeObjectponieważ elementy podrzędne klasy, której nie można serializować, mogą być serializowane, ale ich wystąpienia mogą zostać przeniesione z powrotem do klasy nadrzędnej. W rezultacie trzymanie odwołania do czegoś, co nie jest możliwe do serializacji (np. Object), Nie oznacza, że ​​odwołanej instancji nie można naprawdę serializować. Na przykład w

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

Klasa nadrzędna ( Object) nie jest możliwa do serializacji i została zainicjowana przy użyciu konstruktora bez parametrów. Wartość odwołuje x, Stringjest możliwy do serializacji i instrukcja warunkowa będzie działać.

Audrius Meskauskas
źródło
6

Interfejs znaczników w Javie to interfejs bez pól ani metod. Mówiąc prościej, pusty interfejs w Javie nazywany jest interfejsem znaczników. Przykłady interfejsów markerowych są Serializable, Cloneablei Remoteinterfejsów. Służą one do wskazania pewnych informacji kompilatorom lub maszynom JVM. Jeśli więc maszyna JVM zobaczy, że klasa jest Serializable, może wykonać na niej specjalne operacje. Podobnie, jeśli maszyna JVM zobaczy, że jakaś klasa jest implementowana Cloneable, może wykonać pewne operacje w celu obsługi klonowania. To samo dotyczy RMI i Remoteinterfejsu. Krótko mówiąc, interfejs znacznika wskazuje sygnał lub polecenie dla kompilatora lub maszyny JVM.

Powyższe rozpoczęło się jako kopia posta na blogu, ale zostało lekko zredagowane pod kątem gramatyki.

vidya kn
źródło
6
Kopiowanie jest w porządku, ale podaj też źródło: javarevisited.blogspot.com/2012/01/… . Będzie też miło, jeśli nie skopiujesz i nie wkleisz błędów pisowni. :)
Saurabh Patil
6

Zrobiłem prostą demonstrację, aby rozwiązać wątpliwości nr 1 i 2:

Będziemy mieć Movable interfejs, który będzie implementowany przez MobilePhone.javaClass i jeszcze jedną klasę, LandlinePhone.javaktóra NIE implementuje interfejsu Movable

Nasz interfejs znaczników:

package com;

public interface Movable {

}

LandLinePhone.java i MobilePhone.java

 package com;

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

Nasza niestandardowa klasa wyjątków: pakiet 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
    }

Nasza klasa testowa: 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");
}}

Teraz, gdy wykonujemy główną klasę:

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)

Więc która kiedykolwiek klasa implementuje interfejs znacznika Movable przejdzie test, w przeciwnym razie zostanie wyświetlony komunikat o błędzie.

W ten sposób instanceOfwykonuje się sprawdzanie operatora dla serializowalnych , klonowalnych itp

Shashank Bodkhe
źródło
5

a / Interfejs znacznika, jak sugeruje jego nazwa, istnieje tylko po to, aby powiadomić wszystko, co o nim wie, że klasa coś deklaruje. Cokolwiek może być klasami JDK dla Serializableinterfejsu lub dowolną klasą, którą napiszesz jako niestandardową.

b / Jeśli jest to interfejs znacznika, nie powinno to implikować istnienia żadnej metody - lepiej byłoby uwzględnić metodę domniemaną w interfejsie. Można jednak zdecydować się go zaprojektować tak, jak chcesz, jeśli wiesz, dlaczego ty jej potrzebujesz

c / Istnieje niewielka różnica między pustym interfejsem a adnotacją, która nie używa wartości ani parametru. Ale różnica jest taka: adnotacja może zadeklarować listę kluczy / wartości, które będą dostępne w czasie wykonywania.

Serge Ballesta
źródło
3

za. Zawsze postrzegałem je jako wzorzec projektowy i nic specjalnego dla JVM. Użyłem tego wzorca w kilku sytuacjach.

do. Wierzę, że używanie adnotacji do zaznaczania czegoś jest lepszym rozwiązaniem niż używanie interfejsów znaczników. Po prostu dlatego, że interfejsy są w pierwszej kolejności przeznaczone do definiowania wspólnych interfejsów typów / klas. Są częścią hierarchii klasowej.

Adnotacje mają na celu dostarczenie metainformacji do kodu i myślę, że znaczniki to metainformacje. Są więc dokładnie dla tego przypadku użycia.

treeno
źródło
3
  1. Nie ma to nic wspólnego (koniecznie) z JVM i kompilatorami, ma coś wspólnego z każdym kodem, który jest zainteresowany i testuje interfejs danego znacznika.

  2. To decyzja projektowa i nie bez powodu. Zobacz odpowiedź Audriusa Meškauskasa.

  3. Jeśli chodzi o ten konkretny temat, nie sądzę, że jest to kwestia bycia lepszym lub gorszym. Interfejs znacznika robi dobrze, co powinien.

peter.petrov
źródło
Czy możesz dodać link do „odpowiedzi od Audriusa Meškauskasa”? Nie widzę nic na tej stronie o takiej nazwie.
Sarah Messer
2

Głównym celem interfejsów znaczników jest tworzenie specjalnych typów, w których same typy nie mają własnego zachowania.

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

Tutaj save metoda zapewnia, że ​​są zapisywane tylko obiekty klas implementujących interfejs MarkerEntity, dla innych typów InvalidEntityFoundException. Więc tutaj interfejs markera MarkerEntity definiuje typ, który dodaje specjalne zachowanie do klas implementujących go.

Chociaż adnotacje mogą teraz być również używane do oznaczania klas dla niektórych specjalnych zabiegów, ale adnotacje znaczników zastępują wzorzec nazewnictwa, a nie interfejsów Markera.

Ale adnotacje znaczników nie mogą w pełni zastąpić interfejsów znaczników, ponieważ; interfejsy znaczników służą do definiowania typu (jak już wyjaśniono powyżej), podczas gdy adnotacje znaczników nie.

Źródło komentarza interfejsu znacznika

infoj
źródło
1
+1 za pokazanie, że jest on faktycznie używany jako zwykły punkt zaczepienia „bezpieczeństwa”, który programista musi wyraźnie powiedzieć, że można go zapisać w przykładowej bazie danych.
Ludvig W,
1

Przede wszystkim argumentowałbym, że Serializable i Cloneable to złe przykłady interfejsów znaczników. Oczywiście, są to interfejsy z metodami, ale implikują metody, takie jak writeObject(ObjectOutputStream). (Kompilator stworzy writeObject(ObjectOutputStream)dla ciebie metodę, jeśli jej nie zastąpisz, a wszystkie obiekty już to zrobiły clone(), ale kompilator ponownie utworzy clone()dla ciebie prawdziwą metodę, ale z zastrzeżeniami. Oba są dziwnymi skrajnymi przypadkami, które tak naprawdę nie są dobre przykłady projektów.)

Interfejsy znaczników są zwykle używane do jednego z dwóch celów:

1) Jako skrót pozwalający uniknąć zbyt długiego tekstu, co może się zdarzyć w przypadku wielu rodzajów leków generycznych. Załóżmy na przykład, że masz ten podpis metody:

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

To niechlujne i irytujące pisanie, a co ważniejsze, trudne do zrozumienia. Zamiast tego rozważ to:

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

Wtedy twoja metoda wygląda następująco:

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

Nie tylko jest to bardziej przejrzyste, ale możesz teraz Javadoc interfejs Widget, a także łatwiej jest wyszukiwać wszystkie wystąpienia w kodzie Widgetu.

2) Interfejsy znaczników mogą być również używane jako sposób na obejście braku typów przecięć w Javie. W przypadku interfejsu znaczników można wymagać, aby coś było dwóch różnych typów, na przykład w sygnaturze metody. Załóżmy, że masz w swojej aplikacji jakiś widget interfejsu, taki jak opisaliśmy powyżej. Jeśli masz metodę, która wymaga Widżetu, który również pozwala na iterację (jest wymyślona, ​​ale pracuj ze mną tutaj), jedynym dobrym rozwiązaniem jest utworzenie interfejsu znaczników, który rozszerza oba interfejsy:

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

A w swoim kodzie:

public void doSomething(IterableWidget widget) {
    for (String s : widget) { ... }
}
MikeyB
źródło
1
Kompilator nie tworzy żadnych metod, jeśli klasa implementuje Serializablelub Cloneable. Możesz to zweryfikować, przeglądając pliki swoich zajęć. Co więcej, tworzenie „skrótów interfejsów” nie jest tym, o co chodzi w interfejsach znaczników. Jest to naprawdę zła praktyka kodowania, ponieważ wymagałaby stworzenia dodatkowych klas implementacji , aby spełnić dodatkowe interfejsy. Nawiasem mówiąc, Java od dziesięciu lat ma typy przecięć. Dowiedz się więcej o rodzajach generycznych…
Holger
Jeśli chodzi o klonowanie, masz rację. Zmienia to, co robi Object.clone (). To wciąż okropne. Zobacz łącze Josha Blocha. Interfejsy skrótów niekoniecznie są dobrym wzorcem projektowym, ponieważ arbitralnie ograniczasz to, co możesz wysłać do metody. Jednocześnie uważam, że pisanie jaśniejszego, choć nieco bardziej restrykcyjnego kodu jest zwykle rozsądnym kompromisem. Jeśli chodzi o typy przecięć, dotyczy to tylko typów ogólnych i nie można ich denotować, a zatem w wielu przypadkach jest bezużyteczne. Spróbuj mieć zmienną instancji, która jest zarówno możliwa do serializacji, jak i cóż, cokolwiek innego.
MikeyB
0

Jeśli interfejs nie zawiera żadnej metody i poprzez implementację tego interfejsu, jeśli nasz obiekt uzyska jakąś zdolność, tego typu interfejsy nazywane są interfejsami znaczników.

yogesh kumar
źródło