Разница между статическим модификатором и статическим блоком

Кто-то объясняет мне различия между двумя следующими утверждениями?

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

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