Какие классы инициализируются, когда статический метод базового класса вызывается через ссылку на производный класс?

Я думаю, что только базовый класс инициализируется при вызове Derived.f(). Точно так же, как это происходит, когда у нас есть статическое поле (не постоянное во времени компиляции) в базе, а не статический метод. Я сомневаюсь только в том, что JLS не очень ясно это понимает.

class Base {
    static void f() {}
}

class Derived extends Base {

}

Derived.f(); // called legally from some other class

Согласно JLS (см. ниже), создается впечатление, что инициализируется только базовый класс. Но правильно ли я читаю и понимаю JLS?

Я также знаю, что в случае со статическими полями (непостоянными во времени компиляции) инициализируется только класс, в котором статическое поле определено (вызываются статические инициализаторы):

class Base {
    static int x = 1;
}

class Derived extends Base {

}

//somewhere in some other class
int y = Derived.x;  // triggers only Base to be initialized

12.4.1. Когда происходит инициализация (JLS)

Тип класса или интерфейса T будет инициализирован непосредственно перед первым появлением любого из следующего:

T — это класс, и создается экземпляр T.

T — это класс, и вызывается статический метод, объявленный классом T.

Присваивается статическое поле, объявленное T.

Используется статическое поле, объявленное T, и это поле не является постоянной переменной (§4.12.4).

T является классом верхнего уровня (§7.6), и выполняется оператор assert (§14.10), лексически вложенный в T (§8.1.3).

Ссылка на статическое поле (§8.3.1.1) вызывает инициализацию только класса или интерфейса, который фактически объявляет его, даже если на него можно ссылаться через имя подкласса, субинтерфейса или класса, который реализует интерфейс. НО JLS НЕ ЗАЯВЛЯЕТ ЗДЕСЬ ТАКОЕ О СТАТИЧЕСКИХ МЕТОДАХ!

Кроме того, 12.4. Инициализация классов и интерфейсов ясно говорит:

Перед инициализацией класса необходимо инициализировать его непосредственный суперкласс

И исключение из этого правила дает JLS только для статических полей, а не для статических методов!

Инициализация как Base, так и Derived, по-видимому, имеет смысл - если f() использует какие-либо статические поля внутри своего тела, то Derived.f() будет использовать статические поля Derived (если есть) и может использовать статические поля, унаследованные от Base (если Derived не имеет) - что имеет смысл инициализировать оба класса.

В конце концов, простой тест показывает, что инициализируется только базовый класс:

class Base {
    static { System.out.println("Base init"); }

    static void f() {}
}

class Derived extends Base {
    static { System.out.println("Derived init"); }
}

public class Driver {
    public static void main(String[] args)  {
        Derived.f(); // Only "Base init" printed...
    }
}

person Code Complete    schedule 04.02.2018    source источник
comment
Связано?   -  person user202729    schedule 04.02.2018
comment
Под словом инициализировано, что вы имеете в виду?   -  person Amit Bera    schedule 04.02.2018
comment
@AmitBera JLS 12.4.1. Когда происходит инициализация — когда запускаются статические инициализаторы (не конструктор и не инициализаторы экземпляра).   -  person Code Complete    schedule 04.02.2018
comment
@ user202729 - Нет, это не имеет прямого отношения к вопросу.   -  person Code Complete    schedule 04.02.2018
comment
Я не уверен, что именно вы спрашиваете. Не могли бы вы указать, какое утверждение JLS вас смущает?   -  person Ravi    schedule 04.02.2018
comment
Проверьте декомпилированный код, Derived.x можно скомпилировать как Base.x   -  person Enerccio    schedule 04.02.2018
comment
Я предполагаю, что инициализируется только родитель... но спецификации в лучшем случае сбивают с толку!   -  person Janez Kuhar    schedule 04.02.2018
comment
Вы уже выделили соответствующее слово, инициализация происходит, когда «вызывается статический метод, объявленный с помощью T». Таким образом, когда вы вызываете Derived.f(), он по-прежнему вызывает метод объявлено Base.   -  person Holger    schedule 23.02.2018


Ответы (1)


Возможно, путаница связана с тем, как переменные-члены, включая статические переменные-члены, работают в Java. В отличие от методов, переменные-члены в производных классах не наследуют своих аналогов в суперклассе; они просто затеняют их. Таким образом, если у вас есть переменная foo в Base и переменная foo в Derived, то если вы напечатаете эту переменную в Base, вы получите версию в Base, а если вы напечатаете из Derived, вы получите версию в Derived.

public class MyTest {

    public static void main(String[] args) {

        Base.f();
        Derived.f();
    }
}

class Base {
    static void f() {
        System.out.println("bar = " + bar);
    }

    protected static int bar = 1;
}

class Derived extends Base {
    static void f() {
        System.out.println("bar = " + bar);
    }

    private static int bar = 2;
}

дает:

bar = 1
bar = 2

Таким образом, нет причин запускать инициализаторы в Derived при вызове статического метода в Base, потому что Base никогда не сможет получить прямой доступ к переменным в Derived.

person Steve Brandli    schedule 04.02.2018