Подтвердить что ты не робот

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

Учитывая следующие классы:

public abstract class Super {
    protected static Object staticVar;

    protected static void staticMethod() {
        System.out.println( staticVar );
    }
}

public class Sub extends Super {
    static {
        staticVar = new Object();
    }

    // Declaring a method with the same signature here, 
    // thus hiding Super.staticMethod(), avoids staticVar being null
    /*
    public static void staticMethod() {
        Super.staticMethod();
    }
    */
}

public class UserClass {
    public static void main( String[] args ) {
        new UserClass().method();
    }

    void method() {
        Sub.staticMethod(); // prints "null"
    }
}

Я не настроен на такие ответы, как "Потому что это указано в JLS". Я знаю, что, поскольку JLS, 12.4.1 Когда Инициализация Происходит, просто читается:

Класс или тип интерфейса T будет инициализирован непосредственно перед первым вхождением любого из следующих значений:

  • ...

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

  • ...

Меня интересует, есть ли веская причина, почему нет предложения вроде:

  • T является подклассом S, а статический метод, объявленный S, вызывается на T.
4b9b3361

Ответ 1

Я думаю, что это связано с этой частью спецификации jvm:

Каждый кадр (п. 2.6) содержит ссылку на пул констант времени выполнения (п. 2.5.5) для типа текущего метода для поддержки динамической компоновки кода метода. Код файла класса для метода относится к методам, которые должны быть вызваны, и к переменным, к которым можно получить доступ через символические ссылки. Динамическое связывание переводит эти ссылки на символические методы в конкретные ссылки на методы, загружая классы по мере необходимости, чтобы разрешать символы "все еще" undefined, и переводит обращения переменных в соответствующие смещения в структурах хранения, связанных с местоположением этих переменных во время выполнения.

Эта поздняя привязка методов и переменных делает изменения в других классах, которые метод использует с меньшей вероятностью для разрыва этого кода.

В глава 5 в спецификации jvm они также упоминают: Класс или интерфейс C могут быть инициализированы, среди прочего, в результате:

Выполнение любой из инструкций виртуальной машины Java new, getstatic, putstatic или invokestatic, которая ссылается на C (§new, §getstatic, §putstatic, §invokestatic). Эти инструкции ссылаются на класс или интерфейс прямо или косвенно через ссылку на поле или ссылку на метод.

...

После выполнения команды getstatic, putstatic или invokestatic класс или интерфейс, объявивший разрешенное поле или метод, инициализируется, если он еще не был инициализирован.

Мне кажется, что первый бит документации гласит, что любая символическая ссылка просто разрешена и вызывается без учета того, откуда она появилась. Эта документация о разрешении метода имеет следующие значения:

[M] Разрешение этада пытается найти ссылочный метод в C и его суперклассах:

Если C объявляет ровно один метод с именем, указанным ссылкой на метод, и объявление является полиморфным методом подписи (§2.9), то поиск метода преуспевает. Все имена классов, упомянутые в дескрипторе, разрешаются (§5.4.3.1).

Разрешенный метод - это объявление полиморфного метода подписи. C не нужно объявлять метод с дескриптором, указанным в ссылке метода.

В противном случае, если C объявляет метод с именем и дескриптором, указанным ссылкой на метод, поиск метода преуспевает.

В противном случае, если C имеет суперкласс, шаг 2 разрешения метода рекурсивно вызывается в прямом суперклассе C.

Таким образом, тот факт, что он вызвал из подкласса, кажется, просто игнорируется. Почему так? В прилагаемой документации они говорят:

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

В вашем примере вы изменяете состояние Super, когда Sub статически инициализируется. Если инициализация произошла, когда вы вызвали Sub.staticMethod, вы получили бы другое поведение за то, что jvm считает одним и тем же методом. Это может быть несогласованность, о которой они говорили, чтобы избежать.

Кроме того, здесь некоторые из декомпилированных файлов классов классов, которые выполняют staticMethod, показывая использование invokestatic:

Constant pool:
    ...
    #2 = Methodref          #18.#19        // Sub.staticMethod:()V

... 

Code:
  stack=0, locals=1, args_size=1
     0: invokestatic  #2                  // Method Sub.staticMethod:()V
     3: return

Ответ 2

Будьте осторожны в своем названии, статические поля и методы НЕ унаследованы. Это означает, что когда вы комментируете staticMethod() в Sub, Sub.staticMethod() на самом деле вызывает Super.staticMethod(), тогда Sub статический инициализатор не выполняется.

Однако вопрос интереснее, чем я думал с первого взгляда: с моей точки зрения, это не должно компилироваться без предупреждения, точно так же, как при вызове статического метода на экземпляр класса.

EDIT: Как указал @GeroldBroser, первая формулировка этого ответа неверна. Статические методы также наследуются, но никогда не переусердствуют, просто скрываются. Я оставляю ответ как есть для истории.

Ответ 3

JLS специально позволяет JVM избегать загрузки Sub-класса, это в разделе, указанном в вопросе:

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

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

Ответ 4

Причина довольно проста: для JVM преждевременно не выполнять дополнительную работу (Java ленив по своей природе).

Будете ли вы писать Super.staticMethod() или Sub.staticMethod(), вызывается одна и та же реализация. И эта родительская реализация обычно не зависит от подклассов. Статические методы Super не должны получать доступ к элементам Sub, так что точка инициализации Sub тогда?

Ваш пример кажется искусственным и недостаточно продуманным.

Создание подкласса переписать статические поля суперкласса не кажется хорошей идеей. В этом случае результат методов Super будет зависеть от того, какой класс был затронут первым. Это также затрудняет наличие нескольких детей Super со своим поведением. Чтобы сократить его, статические члены не для полиморфизма - то, что говорят принципы ООП.

Ответ 5

по какой-то причине jvm думает, что статический блок не годится, и его не выполнил

Я полагаю, это потому, что вы не используете какие-либо методы для подкласса, поэтому jvm не видит причин "инициализировать" сам класс, вызов метода статически привязан к родительскому во время компиляции - существует поздняя привязка для статических методов

http://ideone.com/pUyVj4

static {
    System.out.println("init");
    staticVar = new Object();
}

Добавьте другой метод и вызовите его перед суб

Sub.someOtherMethod();
new UsersClass().method();

или сделать явно Class.forName("Sub");

Class.forName("Sub");
new UsersClass().method();

Ответ 6

Когда статический блок выполняется Статические инициализаторы

Статический инициализатор, объявленный в классе, выполняется, когда класс инициализируется

когда вы вызываете Sub.staticMethod();, что означает, что класс не инициализирован. Вы просто ссылаетесь

Когда инициализируется класс

Когда класс инициализируется в Java После загрузки класса происходит инициализация класса, что означает инициализацию всех статических членов класса. Класс инициализируется на Java, когда:

1) Экземпляр класса создается с использованием либо ключевого слова new(), либо использования отражения с использованием класса .forName(), которое может вызывать ClassNotFoundException в Java.

2) вызывается статический метод класса.

3) назначено статическое поле класса.

4) используется статическое поле класса, которое не является постоянной переменной.

5), если класс является классом верхнего уровня, и выполняется оператор утверждения, лексически вложенный в класс.

Когда класс загружается и инициализируется в JVM-Java

почему вы получаете null (значение по умолчанию для переменной экземпляра).

    public class Sub extends Super {
    static {
        staticVar = new Object();
    }
    public static void staticMethod() {
        Super.staticMethod();
    }
}

в этом случае класс инициализируется, и вы получаете хэш-код new object(). Если вы не переопределяете staticMethod(), значит, ваш метод суперкласс класса и Sub не инициализируется.