2014-09-19 2 views
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 When Initialization Occurs читает просто:

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

  • ...

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

  • ...

Я заинтересован в том, есть ли хорошая причина, почему это не приговор, как:

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

Вы проверили байт-код? Я предполагаю, что тот же самый байт-код будет сгенерирован, если 'method' вызывает' Sub.staticMethod() 'или' Super.staticMethod() '[если он был общедоступным], что затруднит задачу во время выполнения, статический инициализатор. Впрочем, я не очень хорошо смотрю на байт-код. – ajb

+0

@ajb: Я тоже, но это должно произойти, иначе это не скомпилируется. – Dici

+0

@ajb Байт-код читает '0 invokestatic igb.Sub.staticMethod(): void [22]' или '0 invokestatic igb.Super.staticMethod(): void [22]'. В зависимости от того, какой класс вызывается. –

ответ

1

Я думаю, что он должен делать с this part из Jvm спецификации:

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

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

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

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

...

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

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

[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 
10

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

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

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

+0

Да, это то, к чему я привык. Но с точки зрения пользователя класса 'Sub.staticMethod()' выглядит как staticMethod() ', являющийся методом' Sub'. И поскольку пользователь не должен знать подробности реализации, появление «null» не очень понятно, не так ли? –

+0

@GeroldBroser: С точки зрения пользователя этот код будет скрыт, и останется только документация. При этом пользователь будет отвечать за использование статического метода в классе wron. Это правда, что это может сбивать с толку, поэтому такой код не должен компилироваться, по крайней мере, с предупреждением, на мой взгляд. – Dici

+0

Мне действительно интересно узнать, есть ли причина, по которой компилятор Java позволяет компилировать 'Sub.staticMethod()' вообще. Я вижу причину, позволяющую методам * внутри * 'Sub' вызывать статические методы в' Super' без квалификации класса, но при условии, что вызывается только статический инициализатор 'Super', можете ли вы придумать какую-либо причину внешний класс (например, 'UserClass') для вызова' staticMethod() 'любым способом, кроме' Super.staticMethod() '? Я не могу, с головы до ног. – Sumitsu

0

по какой-то причине JVM считают, что статический блок не хорошо, и его не выполнил

Я считаю, это потому, что вы не используете методы подкласса, поэтому JVM не видит оснований для «INIT» Класс сам вызов метода статически связан с родителем во время компиляции - там позднее связывание для статических методов

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(); 
+0

Да, я знаю об этом. Вот что я хотел бы сказать о JLS 12.4.1. –

+0

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

+0

Он сказал [Указатель] (http: // www .youtube.com/смотреть? v = SYkbqzWVHZI)! ;-) –

3

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

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

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

+0

Да, но мой вопрос касается не статических * полей *, а о том, почему ссылка на метод статического * суперкласса * через подкласс не инициализирует подкласс. Но, похоже, что ваш ответ «избегать ненужных классов нагрузки JVM» применяется также к методам. Хотя, в случае, описанном в моем первоначальном вопросе, я бы не назвал его «ненужным». –

2

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

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

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

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

0

Когда статический блок выполняется Static Initializers

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

при звонке Sub.staticMethod(); это означает cla сс в не initialized.Your просто Другой крупный

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

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

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

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

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

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

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

When a class is loaded and initialized in JVM - Java

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

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

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

0

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

Вот пример снимка экрана. enter image description here

Смежные вопросы