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

17

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

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.
  •   
    
задан Gerold Broser 19.09.2014 в 22:14
источник
  • Вы проверили байт-код? Я предполагаю, что тот же самый байт-код будет сгенерирован, если метод вызывает Sub.staticMethod () или Super.staticMethod () [если он был общедоступным], что затруднило бы сказать во время выполнения, какой должен быть вызван статический инициализатор класса , Впрочем, я не очень хорошо смотрю на байт-код. –  ajb 19.09.2014 в 22:26
  • @ajb: Я тоже, но это должно быть то, что происходит, иначе это не будет компилироваться. –  Dici 19.09.2014 в 22:27
  • @ajb Байт-код читает 0 invokestatic igb.Sub.staticMethod (): void [22] или 0 invokestatic igb.Super.staticMethod (): void [22]. В зависимости от того, какой класс вызывается. –  Gerold Broser 19.09.2014 в 22:41

7 ответов

1

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

  

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

     

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

В главе 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
    
ответ дан heisbrandon 07.04.2016 в 22:35
10

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

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

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

    
ответ дан Dici 19.09.2014 в 22:15
  • Да, это то, к чему я привык. Но с точки зрения класса пользователя Sub.staticMethod () выглядит как staticMethod (), являющийся методом Sub. И поскольку пользователь не должен знать подробности реализации, появление null не очень понятно, не так ли? –  Gerold Broser 19.09.2014 в 22:34
  • @GeroldBroser: С точки зрения пользователя этот код будет скрыт, и останется только документация. При этом пользователь будет отвечать за использование статического метода в классе wron. Это правда, что это может сбивать с толку, поэтому такой код не должен компилироваться, по крайней мере, с предупреждением, на мой взгляд. –  Dici 19.09.2014 в 22:44
  • @Dici На самом деле в Eclipse есть предупреждение: «Статический метод staticMethod () из типа Super должен быть доступен напрямую», и его можно отключить в соответствии с предпочтением «Косвенный доступ к статическому члену». –  Gerold Broser 19.09.2014 в 22:51
  • Причина этого в том, что она относительно распространена (хотя IMO, вероятно, не очень хорошая идея) для абстрактного класса для определения статических методов, которые могут использовать его подклассы, не импортируя их. Например, в JUnit 3.x все тестовые классы должны наследовать от TestCase, а TestCase расширяет junit.framework.Assert с единственной целью - вытащить все методы assertEquals и т. Д. В область подклассов. Теперь, когда у нас есть статический импорт, для этого нет веских оснований. –  Daniel Pryden 19.09.2014 в 22:59
  • @ Dici re your "не следует компилировать без предупреждения, точно так же, как при вызове статического метода на экземпляр класса": Компиляция с javac * .java вообще не показывает никакого предупреждения. Компиляция с javac -Xlint * .java показывает только предупреждение для «статического метода должно быть квалифицировано по типу имени [...], а не выражением« если таковое существует ». С помощью -Werror вы можете сделать это ошибкой. Косвенный статический доступ вообще не подвергается критике. –  Gerold Broser 20.09.2014 в 01:42
Показать остальные комментарии
3

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

  

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

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

    
ответ дан Nathan Hughes 19.09.2014 в 23:20
  • Да, но мой вопрос касается не статических полей, а фона, почему ссылка на статический метод суперкласса через подкласс не инициализирует подкласс. Но, скорее всего, ваш ответ «избегать ненужных классов нагрузки JVM» применим и к методам. Хотя, в случае, описанном в моем первоначальном вопросе, я бы не назвал его «ненужным». –  Gerold Broser 20.09.2014 в 00:33
2

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

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

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

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

    
ответ дан apangin 06.04.2016 в 23:58
0

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

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

Ссылка

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

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

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

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

Class.forName("Sub");
new UsersClass().method();
    
ответ дан Kalpesh Soni 19.09.2014 в 22:54
  • Да, я знаю об этом. Вот что я хотел бы сказать о JLS 12.4.1. –  Gerold Broser 19.09.2014 в 23:01
  • , потому что если T является подклассом S, и вам не нужно что-либо из T, jvm не видит необходимости его загружать, вы просто используете его как указатель, указывающий на родительский –  Kalpesh Soni 19.09.2014 в 23:09
  • Он сказал указатель! ;-) –  Gerold Broser 19.09.2014 в 23:19
  • Богохульство! что vid веселый –  Kalpesh Soni 19.09.2014 в 23:28
  • «вызов метода статически связан с родителем во время компиляции», кажется, не является истинным (в случае использования Sub), если мы посмотрим на байт-код, который я опубликовал в своем первом комментарии к моему оригинальному вопросу. –  Gerold Broser 20.09.2014 в 02:09
Показать остальные комментарии
0

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

  

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

, когда вы вызываете 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();
    }
}

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

    
ответ дан Khan Abdulrehman 11.04.2016 в 11:36
0

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

Вот пример снимка экрана.

    
ответ дан Junbang Huang 11.04.2016 в 22:29