Разница между статическим модификатором и статическим блоком
Кто-то объясняет мне различия между двумя следующими утверждениями?
A static final
переменная, инициализированная блоком кода static
:
private static final String foo;
static { foo = "foo"; }
A static final
переменная, инициализированная назначением:
private static final String foo = "foo";
Ответы
Ответ 1
В этом примере есть одна тонкая разница - в вашем первом примере foo
не определяется как константа времени компиляции, поэтому ее нельзя использовать как случай в блоках switch
(и wouldn не встраиваться в другой код); в вашем втором примере это, есть. Так, например:
switch (args[0]) {
case foo:
System.out.println("Yes");
break;
}
Это допустимо, когда foo
считается постоянным выражением, но не когда оно "просто" статической конечной переменной.
Однако, статические блоки инициализатора обычно используются, когда у вас более сложный код инициализации - например, заполнение коллекции.
Сроки для инициализации описаны в JLS 12.4.2; любые статические конечные поля, которые считаются константами времени компиляции, сначала инициализируются (этап 6), а инициализаторы выполняются позже (этап 9); все инициализаторы (независимо от того, являются ли они инициализаторами полей или статическими инициализаторами) выполняются в текстовом порядке.
Ответ 2
private static final String foo;
static { foo ="foo";}
Значение foo
инициализируется при загрузке класса и запускаются статические инициализаторы.
private static final String foo = "foo";
Здесь значение foo
будет константой времени компиляции. Таким образом, на самом деле "foo"
будет доступен как часть самого байтового кода.
Ответ 3
В II случае значение foo - раннее связывание, то есть компилятор идентифицирует и присваивает значение foo переменной FOO
, которая не может быть изменена, и это будет доступным отдельно с байтом- самого кода.
private static final String FOO = "foo";
и в I-м случае значение foo инициализирует сразу после загрузки класса как очень первое присваивание до назначения переменной экземпляра, также здесь вы можете перехватывать исключения или статическое поле может назначать вызовом статических методов в статическом блоке.
private static final String FOO;
static { FOO ="foo";}
Поэтому всякий раз, когда возникает условие, приходящее , когда компилятору необходимо идентифицировать значение переменной foo, условие II будет работать, для ex- как значение case: в случаях переключения.
Ответ 4
JLS описывает несколько специальных способов поведения постоянных переменных, которые являются final
переменными (независимо от того, static
или нет), которые инициализируются постоянными выражениями String
или примитивным типом.
Постоянные переменные имеют большое различие по отношению к двоичной совместимости: значения константных переменных становятся частью API класса, насколько это касается компилятора.
Пример:
class X {
public static final String XFOO = "xfoo";
}
class Y {
public static final String YFOO;
static { YFOO = "yfoo"; }
}
class Z {
public static void main(String[] args) {
System.out.println(X.XFOO);
System.out.println(Y.YFOO);
}
}
Здесь XFOO
- это "постоянная переменная", а YFOO
- нет, но они в остальном эквивалентны. Класс Z
печатает каждый из них. Скомпилируйте эти классы, затем разоберите их с помощью javap -v X Y Z
, и вот результат:
Класс X:
Constant pool:
#1 = Methodref #3.#11 // java/lang/Object."<init>":()V
#2 = Class #12 // X
#3 = Class #13 // java/lang/Object
#4 = Utf8 XFOO
#5 = Utf8 Ljava/lang/String;
#6 = Utf8 ConstantValue
#7 = String #14 // xfoo
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = NameAndType #8:#9 // "<init>":()V
#12 = Utf8 X
#13 = Utf8 java/lang/Object
#14 = Utf8 xfoo
{
public static final java.lang.String XFOO;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: String xfoo
X();
descriptor: ()V
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
}
Класс Y:
Constant pool:
#1 = Methodref #5.#12 // java/lang/Object."<init>":()V
#2 = String #13 // yfoo
#3 = Fieldref #4.#14 // Y.YFOO:Ljava/lang/String;
#4 = Class #15 // Y
#5 = Class #16 // java/lang/Object
#6 = Utf8 YFOO
#7 = Utf8 Ljava/lang/String;
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 <clinit>
#12 = NameAndType #8:#9 // "<init>":()V
#13 = Utf8 yfoo
#14 = NameAndType #6:#7 // YFOO:Ljava/lang/String;
#15 = Utf8 Y
#16 = Utf8 java/lang/Object
{
public static final java.lang.String YFOO;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
Y();
descriptor: ()V
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #2 // String yfoo
2: putstatic #3 // Field YFOO:Ljava/lang/String;
5: return
}
Класс Z:
Constant pool:
#1 = Methodref #8.#14 // java/lang/Object."<init>":()V
#2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Class #17 // X
#4 = String #18 // xfoo
#5 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#6 = Fieldref #21.#22 // Y.YFOO:Ljava/lang/String;
#7 = Class #23 // Z
#8 = Class #24 // java/lang/Object
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 main
#13 = Utf8 ([Ljava/lang/String;)V
#14 = NameAndType #9:#10 // "<init>":()V
#15 = Class #25 // java/lang/System
#16 = NameAndType #26:#27 // out:Ljava/io/PrintStream;
#17 = Utf8 X
#18 = Utf8 xfoo
#19 = Class #28 // java/io/PrintStream
#20 = NameAndType #29:#30 // println:(Ljava/lang/String;)V
#21 = Class #31 // Y
#22 = NameAndType #32:#33 // YFOO:Ljava/lang/String;
#23 = Utf8 Z
#24 = Utf8 java/lang/Object
#25 = Utf8 java/lang/System
#26 = Utf8 out
#27 = Utf8 Ljava/io/PrintStream;
#28 = Utf8 java/io/PrintStream
#29 = Utf8 println
#30 = Utf8 (Ljava/lang/String;)V
#31 = Utf8 Y
#32 = Utf8 YFOO
#33 = Utf8 Ljava/lang/String;
{
Z();
descriptor: ()V
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #4 // String xfoo
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: getstatic #6 // Field Y.YFOO:Ljava/lang/String;
14: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
17: return
}
В разборке заметьте, что различия между X
и Y
работают глубже синтаксического сахара:
-
XFOO
имеет атрибут ConstantValue
, означающий, что его значение является константой времени компиляции. В то время как YFOO
не использует и использует блок static
с инструкцией putstatic
для инициализации значения во время выполнения.
-
Константа String
"xfoo"
стала частью пула констант класса Z
, но "yfoo"
не имеет.
-
Z.main
использует команду ldc
(load constant) для загрузки "xfoo"
в стек непосредственно из собственного пула констант, но использует команду getstatic
для загрузки значения Y.YFOO
.
Другие отличия вы найдете:
-
Если вы измените значение XFOO
и перекомпилируете X.java
, но не Z.java
, у вас возникнет проблема: class Z
все еще использует старое значение. Если вы измените значение YFOO
и перекомпилируете Y.java
, класс Z
использует новое значение, перекомпилируете ли вы Z.java
или нет.
-
Если вы полностью удаляете файл X.class
, класс Z
работает корректно. Z
не имеет зависимости от времени выполнения X
. Если вы удаляете файл Y.class
, класс Z
не может инициализироваться с помощью ClassNotFoundException: Y
.
-
Если вы создаете документацию для классов с помощью javadoc, страница "Constant Field Values" будет документировать значение XFOO
, но не значение YFOO
.
JLS описывает вышеупомянутые переменные, которые имеют постоянные переменные, для скомпилированных файлов классов в §13.1.3:
Ссылка на поле, которое является постоянной переменной (§4.12.4), должно быть разрешено во время компиляции до значения V, обозначенного инициализатором постоянной переменной.
Если такое поле static
, то никакая ссылка на поле не должна присутствовать в коде в двоичном файле, включая класс или интерфейс, которые объявили это поле. Такое поле всегда должно быть инициализировано (§12.4.2); начальное значение по умолчанию для поля (если оно отличается от V) никогда не должно наблюдаться.
Если такое поле не является static
, то никакая ссылка на поле не должна присутствовать в коде в двоичном файле, кроме класса, содержащего это поле. (Это будет класс, а не интерфейс, поскольку интерфейс имеет только поля static
.) Класс должен иметь код для установки значения поля V во время создания экземпляра (§12.5).
И в §13.4.9:
Если поле является постоянной переменной (§4.12.4) и, кроме того, static
, то удаление ключевого слова final
или изменение его значения не нарушит совместимость с ранее существовавшими двоичными файлами, заставив их не запускать, но они не будут видеть никакого нового значения для использования поля, если они не перекомпилированы.
[...]
Лучший способ избежать проблем с "непостоянными константами" в широко распространенном коде - использовать постоянные переменные static
только для значений, которые поистине маловероятны. Помимо истинных математических констант, мы рекомендуем, чтобы исходный код очень экономно использовал постоянные переменные static
.
Результат заключается в том, что если ваша публичная библиотека предоставляет любые постоянные переменные, вы никогда не должны изменять свои значения, если ваша новая версия библиотеки в противном случае должна быть совместима с кодом, скомпилированным в старых версиях библиотеки. Это не обязательно приведет к ошибке, но существующий код, вероятно, сработает, так как он будет иметь устаревшие идеи о значениях констант. (Если ваша новая версия библиотеки нуждается в повторной компиляции для классов, которые используют ее для перекомпиляции, то изменение констант не вызывает этой проблемы.)
Таким образом, инициализация константы блоком дает вам больше свободы для изменения его значения, поскольку это предотвращает внедрение компилятора в другие классы.
Ответ 5
Единственное различие - время инициализации.
Java сначала инициализирует элементы, а затем статические блоки.
Ответ 6
Дополнительный аспект: рассмотрим случай, когда у вас несколько статических полей, и да, это угловой случай...
Как указано в ответе Джона Скита, JLS определяет точный порядок инициализации.
Однако, если по какой-то причине вам нужно инициализировать несколько статических атрибутов в определенном порядке, вы можете сделать так, чтобы последовательность инициализации была четко видна в коде.
При использовании прямой инициализации поля: некоторые формирователи кода (и разработчики) могут в какой-то момент решить сортировать поля по-разному, это будет напрямую влиять на то, как поля инициализируются и внедряют нежелательные эффекты.
Кстати, если вы хотите следовать общим правилам кодирования java, вы должны использовать заглавные буквы при определении констант (окончательные статические поля).
--- отредактированный комментарий Джон Скит комментарии ---
Ответ 7
Статический блок дает вам больше, чем просто утверждение. В этом конкретном случае одно и то же.
Статический раздел будет выполняться во время загрузки класса, до того, как будут созданы какие-либо экземпляры. Вы можете вызвать методы здесь и назначить их результаты статическим полям. И вы можете перехватывать исключения в статических блоках.