Статические конечные значения Java заменены в коде при компиляции?

В java, скажем, у меня есть следующее

==fileA.java==
class A
{  
    public static final int SIZE = 100;
}  

Затем в другом файле я использую это значение

==fileB.java==  
import A;
class b
{
      Object[] temp = new Object[A.SIZE];
}

Когда это компилируется, SIZE заменяется значением 100, так что, если бы я заменил FileA.jar, но не FileB.jar, массив объектов получил бы новое значение или он был бы жестко запрограммирован на 100, потому что это значение когда он был первоначально построен?


person Without Me It Just Aweso    schedule 02.03.2011    source источник
comment
ты имеешь в виду new Object[A.SIZE]; ?   -  person Bala R    schedule 03.03.2011
comment
Здесь вы должны получить ошибку компилятора.   -  person Sid    schedule 03.03.2011


Ответы (10)


Да, компилятор Java заменяет статические постоянные значения, такие как SIZE в вашем примере, их литеральными значениями.

Таким образом, если вы позже измените SIZE в классе A, но не перекомпилируете класс b, вы все равно увидите старое значение в классе b. Вы можете легко проверить это:

файл A.java

public class A {
    public static final int VALUE = 200;
}

файл B.java

public class B {
    public static void main(String[] args) {
        System.out.println(A.VALUE);
    }
}

Скомпилируйте A.java и B.java. Теперь запустите: java B

Измените значение в A.java. Перекомпилируйте A.java, но не B.java. Запустите снова, и вы увидите, что печатается старое значение.

person Jesper    schedule 02.03.2011
comment
Это то, что я думал, что это будет делать, но есть также несколько ответов ниже, говорящих об обратном ... у кого-нибудь есть какая-либо документация, подтверждающая любое направление? - person Without Me It Just Aweso; 03.03.2011
comment
Есть ли способ избежать компилятора для этого? - person landry; 13.03.2013
comment
@jesper: Один из лучших ответов, которые я видел по этому вопросу. Мне очень помог! - person Giri; 24.01.2017

Вы можете предотвратить компиляцию константы в B, выполнив

class A
{  
    public static final int SIZE;

    static 
    {
        SIZE = 100;
    }
}  
person MeBigFatGuy    schedule 02.03.2011
comment
Спасибо: это помогло мне в странном сценарии: я использую ikvm для компиляции java в .NET, и мои статические конечные поля были отображены как «const» в C # (что демонстрирует такое же поведение встраивания). Я хотел, чтобы ikvm генерировал «статические только для чтения», которые являются постоянными, но не встроены в зависимые сборки. Я применил ваше решение к моему Java-коду, и ikvm изящно сгенерировал общедоступную статическую информацию только для чтения в SIZE = 100; что нормально в С#! - person odalet; 07.10.2014
comment
Привет @MeBigFatGuy Не могли бы вы объяснить, что происходит за кулисами, когда мы определяем встроенную константу, например final static final String str = какая-то большая строка по сравнению со static { str = какая-то большая строка; } Почему статические константы компилируются в классы, которые на них ссылаются? Любая помощь будет приятной - person Lalit Rao; 17.08.2017
comment
Когда вы определяете общедоступную статическую конечную строку, все, что происходит, это то, где бы вы ни ссылались на это поле, компилятор вставит код операции LDC, ссылающийся на пул констант этого класса, в котором выдается LDC. Таким образом, если вы определяете поле в одном классе, но ссылаетесь на эту переменную в 100 классах, каждый из этих других 100 классов получит копию этой строки в каждом из пулов констант этого класса, а эти другие классы не будут ссылаться на исходный класс. чтобы получить значение строки. Это может вызвать проблемы, если вы измените и скомпилируете исходный класс без компиляции 100 других классов. - person MeBigFatGuy; 21.08.2017
comment
Если рассматриваемая строка действительно массивна, у вас будет куча действительно больших файлов классов. Какой бы класс ни ссылался на это поле, он будет большим. Если вы действительно имеете дело с массивными строками, вам, вероятно, следует поместить их в файлы свойств, которые находятся в пути к классам. - person MeBigFatGuy; 21.08.2017

Ву - каждый день узнаешь что-то новое!

Взято из спецификации Java...

Примечание. Если примитивный тип или строка определены как константы и значение известно во время компиляции, компилятор заменяет имя константы везде в коде ее значением. Это называется константой времени компиляции. Если значение константы во внешнем мире изменится (например, если законодательно установлено, что число пи на самом деле должно быть 3,975), вам потребуется перекомпилировать все классы, использующие эту константу, чтобы получить текущее значение.

person mmccomb    schedule 02.03.2011
comment
Действительно? Замена jar подразумевает замену класса, который компилируется. Таким образом, это утверждение не подтверждает ваше утверждение «не заменено». Быстрая декомпиляция файла класса покажет правильный ответ. - person Brent Worden; 03.03.2011

Еще один способ доказать, что это поведение — посмотреть на сгенерированный байт-код. Когда константа "маленькая" (предположительно ‹ 128):

public B();
  Code:
   0:   aload_0
   1:   invokespecial   #10; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   bipush  42
   7:   anewarray       #3; //class java/lang/Object
   10:  putfield        #12; //Field temp:[Ljava/lang/Object;
   13:  return

}

(Я использовал 42 вместо 100, чтобы он больше выделялся). В данном случае он явно подставляется в байт-коде. Но, скажем, константа «больше». Затем вы получаете байт-код, который выглядит так:

public B();
  Code:
   0:   aload_0
   1:   invokespecial   #10; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   ldc     #12; //int 86753098
   7:   anewarray       #3; //class java/lang/Object
   10:  putfield        #13; //Field temp:[Ljava/lang/Object;
   13:  return

Когда он больше, используется код операции «ldc», который согласно документация JVM "байт без знака, который должен быть действительным индексом в пуле констант времени выполнения текущего класса".

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

person James Kingsbery    schedule 02.03.2011
comment
Вы можете получить этот байт-код из файла *.class с помощью инструмента javap, входящего в состав JDK. - person Jesper; 03.03.2011

Важной концепцией здесь является то, что поле static final инициализируется константой времени компиляции, как определено в JLS. Используйте непостоянный инициализатор (или не-2_ или не-final), и он не будет скопирован:

public static final int SIZE = null!=null?0: 100;

(null не является константой времени компиляции.)

person Tom Hawtin - tackline    schedule 02.03.2011
comment
Я думаю создать фиктивный метод CONST(), а затем выполнить SIZE = CONST(100); было бы легче читать. stackoverflow.com/a/12065326/290254 - person Julius Musseau; 22.08.2012

На самом деле я столкнулся с этой странностью некоторое время назад.

Это скомпилирует "100" непосредственно в класс b. Если вы просто перекомпилируете класс A, это не обновит значение в классе B.

Кроме того, компилятор может не заметить перекомпиляции класса b (в то время, когда я компилировал отдельные каталоги, а класс B находился в отдельном каталоге, а компиляция каталога a не запускала компиляцию B)

person Bill K    schedule 02.03.2011

В качестве оптимизации компилятор встроит эту переменную final.

Итак, во время компиляции это будет выглядеть так.

class b
{
      Object[] temp = new Object[100];
}
person Robby Pond    schedule 02.03.2011

Следует отметить одну вещь: статическое конечное значение известно во время компиляции, если значение неизвестно во время компиляции, компилятор не будет заменять имя константы везде в коде. с его значением.

  public class TestA {
      // public static final int value = 200;
      public static final int value = getValue();
      public static int getValue() {
        return 100;
      }
  }

public class TestB {
    public static void main(String[] args) {
        System.out.println(TestA.value);
    }
}

сначала скомпилируйте TestA и TestB, запустите TestB

затем измените TestA.getValue(), чтобы он возвращал 200, скомпилируйте TestA, запустите TestB, TestB получит новое значение введите здесь описание изображения

person Jay Zhou    schedule 23.02.2017

Есть исключение: -

Если во время компиляции поле static final имеет значение null, оно не заменяется значением null (которое на самом деле является его значением)

А.ява

class A{
     public static final String constantString = null;
}

Б.ява

class B{
     public static void main(String... aa){
         System.out.println(A.constantString);
     }
}

Скомпилируйте и A.java, и B.java и запустите java B.

Вывод будет null


Теперь обновите A.java следующим кодом и скомпилируйте только этот класс.

class A{
     public static final String constantString = "Omg! picking updated value without re-compilation";
}

Теперь запустите java B.

Вывод будет О боже! выбор обновленного значения без повторной компиляции

person amandeep1991    schedule 10.03.2017

Java оптимизирует такие значения, но только если они находятся в одном классе. В этом случае JVM смотрит в A.SIZE, а не оптимизирует его из-за варианта использования, который вы рассматриваете.

person satinyou    schedule 02.03.2011
comment
какой вариант использования может привести к замене или не замене значения во время компиляции? - person Without Me It Just Aweso; 03.03.2011
comment
это неправильно, если это константа времени компиляции, она будет заменена во время компиляции, независимо от того, в каком классе она определена или используется. - person user85421; 03.03.2011