Можно ли отключить встраивание javac статических окончательных переменных? - PullRequest
53 голосов
/ 19 августа 2010

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

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

Ответы [ 9 ]

44 голосов
/ 19 августа 2010

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

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

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

13 голосов
/ 19 августа 2010

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

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

9 голосов
/ 19 августа 2010

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

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

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

7 голосов
/ 19 августа 2010

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

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

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

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

вместо:

public static final int N = ...;

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

public static int N = ...;

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

2 голосов
/ 07 сентября 2017

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

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

0 голосов
/ 03 июня 2018

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

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

    static {
        INT_VALUE = 1000;
        STRING_VALUE = "foo";
    }
}
0 голосов
/ 13 января 2017

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

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

0 голосов
/ 18 января 2011

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

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

0 голосов
/ 19 августа 2010

Я чувствую, что Java тесно зависит от динамической компиляции, и она не выполняет какой-либо сложной логики компиляции, как C ++.

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

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

-s

...