Коли ініціалізується інтерфейс із методом за замовчуванням?


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, але, схоже, його немає. Я б сказав, що його немає.
Warren Dew

1
Ніколи не пам'ятаю .... більшість випадків, коли вони не розуміють або не мають пояснень, вони зволікають :( .Це трапляється загалом.
NeverGiveUp161

Я думав, що interfaceв Java не слід визначати жодного конкретного методу. Тож я здивований, що InterfaceTypeкод склав.
MaxZoom

Відповіді:


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 та Sotirios, дякую за ваші коментарі. Вони разом із 14 оновленнями (станом на цей текст) щодо коментаря Ассілії усунули мою стурбованість щодо будь-якої можливої ​​несправедливості.
Стюарт Маркс

1
@SotiriosDelimanolis Є кілька помилок, які здаються актуальними, JDK-8043275 та JDK-8043190 , і вони позначені як зафіксовані у 8u40. Однак поведінка, здається, однакова. З цим переплелися і деякі зміни JVM Spec, тому, можливо, виправлення - це щось інше, ніж "відновити старий порядок ініціалізації".
Стюарт Маркс

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 для методів за замовчуванням, можливо, є дві можливості

  • Люди Яви забули розглядати випадок методу за замовчуванням. (Специфікація помилка Doc.)
  • Вони просто називають методи за замовчуванням нестабільним членом інтерфейсу. (Але згадано ні де, знову ж таки Специфікація Doc bug.)

Я шукаю посилання на метод за замовчуванням. Поле було просто продемонструвати, чи був інтерфейс ініціалізований чи ні.
Сотіріос Деліманоліс

@SotiriosDelimanolis Я вказав причину у відповіді на метод за замовчуванням ... але, на жаль, жоден JLS ​​ще не знайдений для методу за замовчуванням.
Не помилка

На жаль, саме це я шукаю. Я відчуваю, що ваша відповідь просто повторює речі, про які я вже говорив у запитанні, тобто. що інтерфейс буде ініціалізований, якщо він містить defaultметод і клас, який реалізує інтерфейс, ініціалізований.
Сотіріос Деліманоліс

Я думаю, що java люди забули розглядати випадок методу за замовчуванням, або вони просто називають методи за замовчуванням як непостійний член інтерфейсу (моє припущення, не можна знайти жодного документа).
Не помилка

1
@KishanSarsechaGajjar: Що ви маєте на увазі під постійним полем в інтерфейсі? Будь-яка змінна / поле в інтерфейсі за замовчуванням є статичною.
Локеш

10

Файл 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: Випадково я знайшов цей Документ про методи за замовчуванням у точці доступу, яка містить цікаву бічну примітку в кінці:

3.7 Інше

Оскільки інтерфейси тепер мають в них байт-код, ми повинні ініціалізувати їх під час ініціалізації класу реалізації.


1
Дякуємо, що перекопали це. (+1) Можливо, новий "крок 7.5" був ненавмисно пропущений із специфікації, або що він був запропонований та відхилений, а реалізація ніколи не була встановлена ​​для його видалення.
Стюарт відзначає

1

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

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

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

Будь-який підклас Fooочікує, що вони побачать 1000 доларів у банку, в будь-якому місці коду підкласу. Тому надклас ініціалізується перед підкласом.

Чи не слід робити те ж саме і для суперінтерфейсів? На жаль, порядок надінтерфейсів не повинен бути суттєвим, тому немає чітко визначеного порядку їх ініціалізації.

Тому нам краще не встановлювати подібні побічні ефекти при ініціалізації інтерфейсу. Зрештою, interfaceне призначені для цих функцій (статичні поля / методи), які ми купуємо для зручності.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.