При поиске ответа на этот вопрос в спецификации языка Java я узнал, что
Перед инициализацией класса должен быть инициализирован его прямой суперкласс, но интерфейсы, реализованные классом, не инициализируются. Точно так же суперинтерфейсы интерфейса не инициализируются до инициализации интерфейса.
Из собственного любопытства попробовал и, как и ожидалось, интерфейс InterfaceType
не инициализировался.
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();
}
Эта программа печатает
implemented method
Однако, если интерфейс объявляет default
метод, инициализация все же происходит. Рассмотрим InterfaceType
интерфейс, заданный как
interface InterfaceType {
public static final ClassInitializer init = new ClassInitializer();
public default void method() {
System.out.println("default method");
}
}
то та же программа выше напечатает
static initializer
implemented method
Другими словами, static
поле интерфейса инициализируется ( шаг 9 в Детальной процедуре инициализации ) и выполняется static
инициализатор инициализируемого типа. Это означает, что интерфейс был инициализирован.
Я не смог найти ничего в JLS, чтобы указать, что это должно произойти. Не поймите меня неправильно, я понимаю, что это должно произойти в случае, если реализующий класс не предоставляет реализацию для метода, но что, если да? Это условие отсутствует в спецификации языка Java, я что-то пропустил или неправильно интерпретирую?
источник
interface
в Java не следует определять какой-либо конкретный метод. Поэтому я удивлен, чтоInterfaceType
код скомпилирован.default
методы .Ответы:
Это очень интересный выпуск!
Кажется, что раздел 12.4.1 JLS должен полностью охватить это. Однако поведение Oracle JDK и OpenJDK (javac и HotSpot) отличается от указанного здесь. В частности, Пример 12.4.1-3 из этого раздела посвящен инициализации интерфейса. Пример следующий:
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; } }
Ожидаемый результат:
1 j=3 jj=4 3
и действительно получаю ожидаемый результат. Однако, если к интерфейсу добавлен метод по умолчанию
I
,interface I { int i = 1, ii = Test.out("ii", 2); default void method() { } // causes initialization! }
вывод изменится на:
1 ii=2 j=3 jj=4 3
что ясно указывает на то, что интерфейс
I
инициализируется там, где его не было раньше! Простого наличия метода по умолчанию достаточно для запуска инициализации. Метод по умолчанию не нужно вызывать, переопределять или даже упоминать, равно как и наличие инициализации триггера абстрактного метода.Я предполагаю, что реализация HotSpot хотела избежать добавления проверки инициализации класса / интерфейса в критический путь
invokevirtual
вызова. До Java 8 и методов по умолчаниюinvokevirtual
никогда не могло закончиться выполнение кода в интерфейсе, поэтому этого не возникало. Можно подумать, что это часть этапа подготовки класса / интерфейса ( JLS 12.3.2 ), который инициализирует такие вещи, как таблицы методов. Но, возможно, это зашло слишком далеко и вместо этого случайно выполнила полную инициализацию.Я поднял этот вопрос в списке рассылки разработчиков компиляторов OpenJDK. Был ответ от Алекса Бакли (редактора JLS), в котором он поднимает дополнительные вопросы, адресованные командам реализации JVM и лямбда-выражения. Он также отмечает, что здесь есть ошибка в спецификации, где говорится, что «T - это класс, и статический метод, объявленный T, вызывается», также должен применяться, если T является интерфейсом. Так что, возможно, здесь есть и ошибки спецификации, и ошибки HotSpot.
Раскрытие информации : я работаю в Oracle над OpenJDK. Если люди думают, что это дает мне несправедливое преимущество в получении награды за этот вопрос, я готов проявить гибкость в этом отношении.
источник
Интерфейс не инициализируется, потому что поле константы
InterfaceType.init
, которое инициализируется непостоянным значением (вызов метода), нигде не используется.Во время компиляции известно, что постоянное поле интерфейса нигде не используется, и интерфейс не содержит какого-либо метода по умолчанию (в java-8), поэтому нет необходимости инициализировать или загружать интерфейс.
Интерфейс будет инициализирован в следующих случаях:
В случае методов по умолчанию вы реализуете
InterfaceType
. Итак, еслиInterfaceType
будет содержать какие-либо методы по умолчанию, он будет НАСЛЕДОВАН (использован) при реализации класса. И инициализация будет на картинке.Но, если вы обращаетесь к постоянному полю интерфейса (которое инициализируется обычным способом), инициализация интерфейса не требуется.
Рассмотрим следующий код.
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(); }
В приведенном выше случае интерфейс будет инициализирован и загружен, потому что вы используете это поле
InterfaceType.init
.Я не привожу пример метода по умолчанию, как вы уже указали в своем вопросе.
Спецификация языка Java и пример приведены в JLS 12.4.1 (пример не содержит методов по умолчанию.)
Я не могу найти JLS для методов по умолчанию, могут быть две возможности
источник
default
метод и инициализируется класс, реализующий интерфейс.InstanceKlass.cpp файл из OpenJDK содержит метод инициализации ,
InstanceKlass::initialize_impl
который соответствует Подробная процедура инициализации в JLS, которое аналогично найденной в инициализации раздела в JVM Spec.Он содержит новый шаг, который не упоминается в JLS и не упоминается в книге JVM, упоминаемой в коде:
// 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); .... } } }
Таким образом, эта инициализация была реализована явно как новый шаг 7.5 . Это указывает на то, что эта реализация следовала некоторой спецификации, но похоже, что письменная спецификация на веб-сайте не была обновлена соответствующим образом.
РЕДАКТИРОВАТЬ: В качестве справки, фиксация (с октября 2012 года!), Где соответствующий шаг был включен в реализацию: http://hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362
РЕДАКТИРОВАТЬ2: По совпадению, я нашел этот документ о методах по умолчанию в горячей точке, который содержит интересное примечание в конце:
источник
Я постараюсь сделать так, чтобы инициализация интерфейса не вызывала побочных эффектов побочного канала, от которых зависят подтипы, поэтому независимо от того, является ли это ошибкой или каким-либо образом Java исправляет ее, это не имеет значения. приложение, в котором инициализируются интерфейсы порядка.
В случае a
class
общепризнано, что он может вызывать побочные эффекты, от которых зависят подклассы. Напримерclass Foo{ static{ Bank.deposit($1000); ...
Любой подкласс
Foo
ожидает, что он увидит 1000 долларов в банке в любом месте кода подкласса. Следовательно, суперкласс инициализируется до подкласса.Разве мы не должны сделать то же самое и для суперинтерфейсов? К сожалению, порядок суперинтерфейсов не должен иметь значения, поэтому не существует четко определенного порядка их инициализации.
Так что нам лучше не создавать такого рода побочные эффекты при инициализации интерфейса. В конце концов,
interface
он не предназначен для этих функций (статических полей / методов), которые мы используем для удобства.Поэтому, если мы будем следовать этому принципу, нас не будет волновать, в каком порядке инициализируются интерфейсы.
источник