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

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

9 52

9 ответов:

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

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

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

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

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 как случай в блоке коммутатора, это и должен быть указан в качестве постоянного значения.

, чтобы отключить встраивание нужно принять значения не время компиляции константы (термин ПСБ). Вы можете сделать это без использования функций и создания минимального байт-кода с помощью null в выражении инициализатора.

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

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

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

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

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

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

вместо:

public static final int N = ...;

нет никаких проблем с:

public static int N = ...;

Если N не нужно только для чтения.

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

отказ от ответственности: у меня нет опыта использования jmake.

Я недавно столкнулся с подобным вопрос, и, как было сказано выше, такая вставка может быть обработана с использованием выражений, не связанных с компиляцией, скажем:

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

Я думаю, что это серьезные ошибка. Java-это не C / C++. Есть принцип (или нет) "Компилируй один раз, беги везде".

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

переписать класс A как:

public class A {
    public static final int INT_VALUE;
    public static final String STRING_VALUE;

    static {
        INT_VALUE = 1000;
        STRING_VALUE = "foo";
    }
}

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

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

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

- s