Podczas przeszukiwania specyfikacji języka Java, aby odpowiedzieć na to pytanie , dowiedziałem się tego
Zanim klasa zostanie zainicjowana, jej bezpośrednia nadklasa musi zostać zainicjowana, ale interfejsy implementowane przez klasę nie są inicjowane. Podobnie, superinterfejsy interfejsu nie są inicjowane przed zainicjowaniem interfejsu.
Z własnej ciekawości spróbowałem i zgodnie z oczekiwaniami interfejs InterfaceType
nie został zainicjowany.
public class Example {
public static void main(String[] args) throws Exception {
InterfaceType foo = new InterfaceTypeImpl();
foo.method();
}
}
class InterfaceTypeImpl implements InterfaceType {
@Override
public void method() {
System.out.println("implemented method");
}
}
class ClassInitializer {
static {
System.out.println("static initializer");
}
}
interface InterfaceType {
public static final ClassInitializer init = new ClassInitializer();
public void method();
}
Ten program drukuje
implemented method
Jeśli jednak interfejs deklaruje default
metodę, następuje inicjalizacja. Rozważmy InterfaceType
interfejs podany jako
interface InterfaceType {
public static final ClassInitializer init = new ClassInitializer();
public default void method() {
System.out.println("default method");
}
}
wydrukowałby ten sam program powyżej
static initializer
implemented method
Innymi słowy, static
pole interfejsu jest inicjalizowane ( krok 9 w szczegółowej procedurze inicjalizacji ) i wykonywany jest static
inicjalizator inicjalizowanego typu. Oznacza to, że interfejs został zainicjowany.
W JLS nie mogłem znaleźć niczego, co wskazywałoby na to, że to powinno się wydarzyć. Nie zrozum mnie źle, rozumiem, że powinno to mieć miejsce w przypadku, gdy klasa implementująca nie zapewnia implementacji metody, ale co, jeśli tak jest? Czy tego warunku brakuje w specyfikacji języka Java, czy coś przeoczyłem, czy też interpretuję to nieprawidłowo?
źródło
interface
w Javie nie powinno się definiować żadnej konkretnej metody. Jestem więc zaskoczony, żeInterfaceType
kod się skompilował.default
metody .Odpowiedzi:
To bardzo interesująca kwestia!
Wygląda na to, że sekcja 12.4.1 JLS powinna zająć się tym ostatecznie. Jednak zachowanie Oracle JDK i OpenJDK (javac i HotSpot) różni się od tego, co określono tutaj. W szczególności przykład 12.4.1-3 z tej sekcji obejmuje inicjalizację interfejsu. Przykład w następujący sposób:
interface I { int i = 1, ii = Test.out("ii", 2); } interface J extends I { int j = Test.out("j", 3), jj = Test.out("jj", 4); } interface K extends J { int k = Test.out("k", 5); } class Test { public static void main(String[] args) { System.out.println(J.i); System.out.println(K.j); } static int out(String s, int i) { System.out.println(s + "=" + i); return i; } }
Jego oczekiwany wynik to:
1 j=3 jj=4 3
i rzeczywiście otrzymuję oczekiwany wynik. Jeśli jednak do interfejsu zostanie dodana metoda domyślna
I
,interface I { int i = 1, ii = Test.out("ii", 2); default void method() { } // causes initialization! }
wynik zmienia się na:
1 ii=2 j=3 jj=4 3
co wyraźnie wskazuje, że interfejs
I
jest inicjowany tam, gdzie nie był wcześniej! Sama obecność domyślnej metody wystarczy, aby wywołać inicjalizację. Metoda domyślna nie musi być wywoływana, zastępowana ani nawet wspominana, ani też obecność metody abstrakcyjnej nie powoduje inicjalizacji.Spekuluję, że implementacja HotSpot chciała uniknąć dodawania sprawdzania inicjalizacji klasy / interfejsu do krytycznej ścieżki
invokevirtual
połączenia. Przed Java 8 i metodami domyślnymiinvokevirtual
nigdy nie można było wykonać kodu w interfejsie, więc tak się nie stało. Można by pomyśleć, że jest to część etapu przygotowania klasy / interfejsu ( JLS 12.3.2 ), który inicjuje takie rzeczy, jak tabele metod. Ale być może to poszło za daleko i zamiast tego przypadkowo spowodowało pełną inicjalizację.Mam podniósł tę kwestię na liście mailingowej kompilator-dev OpenJDK. Jest odpowiedź od Alexa Buckleya (redaktora JLS), w której zadaje więcej pytań skierowanych do zespołów wdrożeniowych JVM i lambdy. Zauważa również, że w specyfikacji jest błąd, który mówi, że „T jest klasą, a metoda statyczna zadeklarowana przez T jest wywoływana” powinna również obowiązywać, jeśli T jest interfejsem. Może się więc zdarzyć, że są tu zarówno błędy specyfikacji, jak i HotSpot.
Ujawnienie : pracuję dla Oracle na OpenJDK. Jeśli ludzie uważają, że daje mi to nieuczciwą przewagę w uzyskaniu nagrody związanej z tym pytaniem, jestem gotów być elastyczny w tej kwestii.
źródło
Interfejs nie jest inicjowany, ponieważ pole stałe
InterfaceType.init
, które jest inicjowane przez wartość zmienną (wywołanie metody), nie jest nigdzie używane.W czasie kompilacji wiadomo, że stałe pole interfejsu nie jest nigdzie używane, a interfejs nie zawiera żadnej domyślnej metody (w java-8), więc nie ma potrzeby inicjowania ani ładowania interfejsu.
Interfejs zostanie zainicjowany w następujących przypadkach,
W przypadku domyślnych metod , Ty wdrażają
InterfaceType
. Więc jeśliInterfaceType
będzie zawierał jakiekolwiek domyślne metody, będzie INHERITED (używany) w implementacji klasy. Inicjalizacja będzie widoczna na zdjęciu.Ale jeśli uzyskujesz dostęp do stałego pola interfejsu (które jest inicjowane w normalny sposób), inicjalizacja interfejsu nie jest wymagana.
Rozważ następujący kod.
public class Example { public static void main(String[] args) throws Exception { InterfaceType foo = new InterfaceTypeImpl(); System.out.println(InterfaceType.init); foo.method(); } } class InterfaceTypeImpl implements InterfaceType { @Override public void method() { System.out.println("implemented method"); } } class ClassInitializer { static { System.out.println("static initializer"); } } interface InterfaceType { public static final ClassInitializer init = new ClassInitializer(); public void method(); }
W powyższym przypadku interfejs zostanie zainicjowany i załadowany, ponieważ używasz tego pola
InterfaceType.init
.Nie podaję domyślnego przykładu metody, ponieważ podałeś to już w swoim pytaniu.
Specyfikacja i przykład języka Java są podane w JLS 12.4.1 (przykład nie zawiera metod domyślnych).
Nie mogę znaleźć JLS dla metod domyślnych, mogą istnieć dwie możliwości
źródło
default
metodę, a klasa implementująca interfejs zostanie zainicjowana.Plik instanceKlass.cpp z OpenJDK zawiera metodę inicjalizacji,
InstanceKlass::initialize_impl
która odpowiada szczegółowej procedurze inicjalizacji w JLS, którą analogicznie można znaleźć w pliku inicjalizacji sekcji w specyfikacji JVM.Zawiera nowy krok, o którym nie ma mowy w JLS, a nie w książce JVM, do której odnosi się kod:
// refer to the JVM book page 47 for description of steps ... if (this_oop->has_default_methods()) { // Step 7.5: initialize any interfaces which have default methods for (int i = 0; i < this_oop->local_interfaces()->length(); ++i) { Klass* iface = this_oop->local_interfaces()->at(i); InstanceKlass* ik = InstanceKlass::cast(iface); if (ik->has_default_methods() && ik->should_be_initialized()) { ik->initialize(THREAD); .... } } }
Tak więc ta inicjalizacja została zaimplementowana jawnie jako nowy krok 7.5 . Oznacza to, że ta implementacja była zgodna z pewną specyfikacją, ale wydaje się, że pisemna specyfikacja na stronie internetowej nie została odpowiednio zaktualizowana.
EDYCJA: Jako odniesienie, zatwierdzenie (od października 2012!), W którym odpowiedni krok został uwzględniony w implementacji: http://hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362
EDIT2: Przypadkowo znalazłem ten dokument o domyślnych metodach w hotspocie, który zawiera interesującą uwagę na końcu:
źródło
Postaram się wykazać, że inicjalizacja interfejsu nie powinna powodować żadnych skutków ubocznych kanału bocznego, od których zależą podtypy, dlatego czy jest to błąd, czy nie, lub jakikolwiek sposób naprawi go Java, nie powinno to mieć znaczenia aplikacja, w której są inicjowane interfejsy.
W przypadku a
class
, powszechnie przyjmuje się, że może powodować skutki uboczne, od których zależą podklasy. Na przykładclass Foo{ static{ Bank.deposit($1000); ...
Dowolna podklasa
Foo
spodziewałaby się, że zobaczy w banku 1000 USD w dowolnym miejscu kodu podklasy. Dlatego nadklasa jest inicjowana przed podklasą.Czy nie powinniśmy zrobić tego samego również dla superintefacji? Niestety kolejność superinterfejsów nie ma być znacząca, dlatego nie ma dobrze zdefiniowanej kolejności ich inicjalizacji.
Dlatego lepiej nie ustalać tego rodzaju skutków ubocznych podczas inicjalizacji interfejsu. W końcu
interface
nie jest przeznaczony dla tych funkcji (pól / metod statycznych), które stosujemy dla wygody.Dlatego jeśli będziemy postępować zgodnie z tą zasadą, nie będzie nas obchodziła kolejność inicjalizacji interfejsów.
źródło