Можно ли отключить javac-инфинирование статических конечных переменных?

Статический компилятор Java (javac) строит некоторые статические конечные переменные и приносит значения непосредственно в пул констант. Рассмотрим следующий пример. Класс A определяет некоторые константы (общедоступные статические конечные переменные):

public class A {
    public static final int INT_VALUE = 1000;
    public static final String STRING_VALUE = "foo";
}

Класс B использует эти константы:

public class B {
    public static void main(String[] args) {
        int i = A.INT_VALUE;
        System.out.println(i);
        String s = A.STRING_VALUE;
        System.out.println(s);
    }
}

Когда вы компилируете класс B, javac получает значения этих констант из класса A и строит эти значения в B.class. В результате, зависимость B от класса A во время компиляции стирается из байт-кода. Это довольно своеобразное поведение, потому что вы выпекаете значения этих констант во время компиляции. И вы подумали бы, что это одна из самых простых вещей, которые компилятор JIT может выполнять во время выполнения.

Есть ли какой-либо способ или какой-либо скрытый параметр компилятора, который позволяет отключить это вложение javac? Для фона мы изучаем анализ байт-кода для целей зависимостей, и это один из немногих случаев, когда анализ байт-кода не позволяет обнаружить зависимости времени компиляции. Спасибо!

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

Ответ 1

Пункт 93 Java Puzzlers (Джошуа Блох) говорит, что вы можете обойти это, не допуская, чтобы конечное значение считалось константой. Например:

public class A {
  public static final int INT_VALUE = Integer.valueOf(1000).intValue();
  public static final String STRING_VALUE = "foo".toString();
}

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

Ответ 2

Я так не верю. Простейшим обходным решением было бы разоблачить их как свойства, а не поля:

public class A {
    private static final int INT_VALUE = 1000;
    private static final String STRING_VALUE = "foo";

    public static int getIntValue() {
        return INT_VALUE;
    }
    public static String getStringValue() {
        return STRING_VALUE;
    }
}

Не забывайте, что в некоторых случаях вложение имеет важное значение для использования значения - например, если вы должны использовать INT_VALUE как случай в блоке коммутатора, это должно быть указано как постоянное значение.

Ответ 3

Чтобы прекратить встраивание, вам нужно сделать значения некомпилируемых констант времени (термин JLS). Вы можете сделать это без использования функций и создания минимума байт-кода, используя null в выражении инициализации.

public static final int INT_VALUE = null!=null?0: 1000;

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

Ответ 4

JLS 13.4.9 касается этой проблемы. Их рекомендация состоит в том, чтобы в основном избежать констант времени компиляции, если значение каким-либо образом может измениться.

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

Лучший способ избежать проблем с "непостоянные константы" в широко распространенный код - объявлять поскольку константы времени компиляции только значения которые действительно вряд ли когда-либо будут изменение. За исключением истины математические константы, мы рекомендуем этот исходный код очень экономно использует переменных класса, объявленных статический и конечный. Если доступно только для чтения характер финала необходим, лучше выбор заключается в объявлении частного статического переменная и подходящий аксессуар чтобы получить его значение. Таким образом, мы рекомендуем:

private static int N;
public static int getN() { return N; }

а не:

public static final int N = ...;

Нет проблем с:

public static int N = ...;

если N не требуется только для чтения.

Ответ 5

jmake - проект с открытым исходным кодом, который утверждает, что выполняет всю работу по отслеживанию зависимостей между файлами Java и поэтапной компиляции минимальный набор необходимых файлов. Он утверждает, что правильно обрабатывает изменения в статических конечных константах, хотя иногда требует, чтобы весь проект был перекомпилирован. Он даже обрабатывает изменения с меньшей степенью детализации, чем файлы классов; если (например) подпись метода C.m() изменяется, то она перекомпилирует классы, которые фактически зависят от m(), а не все классы, которые используют C.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: у меня нет опыта использования jmake.

Ответ 6

Недавно я столкнулся с аналогичной проблемой , и, как было сказано выше, такую ​​встраивание можно использовать с использованием выражений, не содержащих компиляцию, например:

public final class A {

    public static final int INT_VALUE = constOf(1000);
    public static final String STRING_VALUE = constOf("foo");

}

где семейство методов constOf является просто:

// @formatter:off
public static boolean constOf(final boolean value) { return value; }
public static byte constOf(final byte value) { return value; }
public static short constOf(final short value) { return value; }
public static int constOf(final int value) { return value; }
public static long constOf(final long value) { return value; }
public static float constOf(final float value) { return value; }
public static double constOf(final double value) { return value; }
public static char constOf(final char value) { return value; }
public static <T> T constOf(final T value) { return value; }
// @formatter:on

Это немного короче, чем другие предложения, такие как Integer.valueOf(1000).intValue() или null!=null?0: 1000

Ответ 7

Я думаю, что это серьезная ошибка. Java не является C/С++. Существует принцип (или нет) "Скомпилировать один раз, работать везде".

В этом случае, когда класс А изменяется. Любые классы, ссылающиеся на A.CONST_VALUE, должны быть перекомпилированы, и они едва ли знают, изменен ли класс A.

Ответ 8

Я чувствую, что java сильно зависит от динамической компиляции, и она не делает никакой логики компиляции, как С++.

вы можете попробовать некоторые параметры с JIT-компиляторами, которые оптимизируют время выполнения, которые могут иметь некоторые опции для отключения/включения этого.

по умолчанию javac вы не можете получить эту опцию. вы должны использовать 1. некоторый тип графика зависимости, например, расширяет или реализует 2. Использование метода на основе ссылок.

-s