Когда инициализируется интерфейс с методом по умолчанию?

94

При поиске ответа на этот вопрос в спецификации языка 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, я что-то пропустил или неправильно интерпретирую?

Сотириос Делиманолис
источник
4
Я предполагаю, что такие интерфейсы считаются абстрактными классами с точки зрения порядка инициализации. Я написал это как комментарий, так как не уверен, правильно ли это утверждение :)
Алексей Малев
Он должен быть в разделе 12.4 JLS, но его там нет. Я бы сказал, что его нет.
Уоррен Дью
1
Неважно .... в большинстве случаев, когда они не понимают или не имеют объяснения, они будут голосовать против :(. Это обычно происходит на SO.
NeverGiveUp161
Я думал, что interfaceв Java не следует определять какой-либо конкретный метод. Поэтому я удивлен, что InterfaceTypeкод скомпилирован.
MaxZoom
@MaxZoom Java 8 позволяет использовать defaultметоды .
Сотириос Делиманолис

Ответы:

85

Это очень интересный выпуск!

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

Стюарт Маркс
источник
6
Я попросил официальные источники. Я не думаю, что это становится более официальным, чем это. Дайте ему два дня, чтобы увидеть все, что происходит.
Сотириос Делиманолис
48
@StuartMarks " Если люди думают, что это дает мне несправедливое преимущество и т. Д. " => Мы здесь, чтобы получить ответы на вопросы, и это идеальный ответ!
assylias
2
Примечание: спецификация JVM содержит описание, аналогичное описанию JLS: docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5 Это также должно быть обновлено .
Marco13
2
@assylias и Сотириос, спасибо за ваши комментарии. Они, наряду с 14 положительными голосами (на момент написания этой статьи) за комментарий Ассилиа, сняли мои опасения по поводу любой потенциальной несправедливости.
Стюарт Маркс
1
@SotiriosDelimanolis Есть пара ошибок, которые кажутся актуальными, JDK-8043275 и JDK-8043190 , и они помечены как исправленные в 8u40. Однако поведение кажется таким же. Были также некоторые изменения спецификации JVM, связанные с этим, так что, возможно, исправление представляет собой нечто иное, чем «восстановление старого порядка инициализации».
Стюарт Маркс
13

Интерфейс не инициализируется, потому что поле константы InterfaceType.init, которое инициализируется непостоянным значением (вызов метода), нигде не используется.

Во время компиляции известно, что постоянное поле интерфейса нигде не используется, и интерфейс не содержит какого-либо метода по умолчанию (в java-8), поэтому нет необходимости инициализировать или загружать интерфейс.

Интерфейс будет инициализирован в следующих случаях:

  • Постоянное поле используется в вашем коде.
  • Интерфейс содержит метод по умолчанию (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 для методов по умолчанию, могут быть две возможности

  • Люди Java забыли рассмотреть случай метода по умолчанию. (Ошибка в спецификации.)
  • Они просто называют методы по умолчанию непостоянными членами интерфейса. (Но не упоминалось где, опять ошибка в спецификации.)
Не ошибка
источник
Я ищу ссылку на метод по умолчанию. Поле было просто для демонстрации того, инициализирован интерфейс или нет.
Сотириос Делиманолис
@SotiriosDelimanolis Я упомянул причину в ответе на метод по умолчанию ... но, к сожалению, какой-либо JLS еще не найден для метода по умолчанию.
Не ошибка
К сожалению, это то, что я ищу. Я чувствую, что ваш ответ просто повторяет то, что я уже сказал в вопросе, т.е. что интерфейс будет инициализирован, если он содержит defaultметод и инициализируется класс, реализующий интерфейс.
Сотириос Делиманолис
Я думаю, что люди java забыли рассмотреть случай метода по умолчанию, или они просто ссылаются на методы по умолчанию как на непостоянный член интерфейса (мое предположение, не может найти ни в одном документе).
Не ошибка
1
@KishanSarsechaGajjar: Что вы имеете в виду под непостоянным полем в интерфейсе? Любая переменная / поле в интерфейсе по умолчанию является static final.
Локеш
10

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: По совпадению, я нашел этот документ о методах по умолчанию в горячей точке, который содержит интересное примечание в конце:

3.7 Разное

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

Marco13
источник
1
Спасибо, что раскопали это. (+1) Может случиться так, что новый «шаг 7.5» был случайно пропущен из спецификации, или что он был предложен и отклонен, а реализация никогда не была исправлена ​​для его удаления.
Стюарт Маркс
1

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

В случае a classобщепризнано, что он может вызывать побочные эффекты, от которых зависят подклассы. Например

class Foo{
    static{
        Bank.deposit($1000);
...

Любой подкласс Fooожидает, что он увидит 1000 долларов в банке в любом месте кода подкласса. Следовательно, суперкласс инициализируется до подкласса.

Разве мы не должны сделать то же самое и для суперинтерфейсов? К сожалению, порядок суперинтерфейсов не должен иметь значения, поэтому не существует четко определенного порядка их инициализации.

Так что нам лучше не создавать такого рода побочные эффекты при инициализации интерфейса. В конце концов, interfaceон не предназначен для этих функций (статических полей / методов), которые мы используем для удобства.

Поэтому, если мы будем следовать этому принципу, нас не будет волновать, в каком порядке инициализируются интерфейсы.

Чжун Ю
источник