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

при поиске по спецификации языка 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 ответов


это очень интересный вопрос!

кажется JLS раздел 12.4.1 должен охватить это окончательно. Однако поведение 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 инициализируется там, где этого не было раньше! Простое наличие метода по умолчанию достаточно для запуска инициализации. Метод по умолчанию не должен вызываться или переопределяться или даже упоминаться, а также наличие абстрактного метода не инициализирует инициализацию.

мое предположение, что точка доступа реализация хотела избежать добавления проверки инициализации класса / интерфейса в критический путь invokevirtual звонок. До Java 8 и методов по умолчанию,invokevirtual никогда не мог закончить выполнение кода в интерфейсе, так это не возникает. Можно подумать, что это часть этапа подготовки класса / интерфейса (JLS 12.3.2), который инициализирует такие вещи, как таблицы методов. Но, возможно, это зашло слишком далеко и случайно вместо этого сделала полную инициализацию.

Я поднял этот вопрос в списке рассылки компилятора OpenJDK-dev. Там был ответ от Алекса Бакли (редактор JLS), в котором он поднимает больше вопросов, направленных на команды реализации JVM и lambda. Он также отмечает, что в спецификации есть ошибка, где говорится, что "T-класс, и статический метод, объявленный T, вызывается", также должен применяться, если t-интерфейс. Так что, возможно, есть и спецификация и точка ошибки здесь.

разоблачение: я работаю в Oracle на OpenJDK. Если люди думают, что это дает мне несправедливое преимущество в получении вознаграждения за этот вопрос, я готов быть гибким.


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

на instanceKlass.cpp файл из OpenJDK содержит метод инициализации InstanceKlass::initialize_impl, что соответствует Полная Процедура Инициализации в JLS, который аналогично находится в инициализации раздел в спецификации JVM.

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

EDIT: в качестве ссылки фиксация (с октября 2012 года!), где соответствующий шаг был включен в реализацию: http://hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362

EDIT2: по совпадению, я нашел это документ о методах по умолчанию в hotspot который содержит интересное примечание в конце:

3.7 разное

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


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

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

class Foo{
    static{
        Bank.deposit(00);
...

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

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

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

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