Окончательные статические объявления Java в локальных классах методов

При объявлении локального внутреннего класса внутри метода, почему законно включать конечные статические строки или ints, но не законно включать другие объекты?

Например:

class Outer {
void aMethod() {
    class Inner {
        final static String name = "compiles";
        final static int ctr = 10; // compiles
        final static Integer intThree = Integer.valueOf(3); // does not compile!
        final static obj objConst = new Object(); // does not compile!
    }

    Inner inner = new Inner();
}
}

Когда я скомпилирую это, я получаю следующее:

InnerExample.java:6: inner classes cannot have static declarations
        final static Integer outer = Integer.valueOf(3);
                             ^
InnerExample.java:7: inner classes cannot have static declarations
        final static Object objConst = new Object();
                            ^

Почему различие? Это потому, что String неизменен? Если да, то не будет ли Integer.valueOf()?

Ответы

Ответ 1

Это потому, что первые два статических элемента назначаются константам времени компиляции примитивного типа или типа String.

Из Спецификация языка Java, раздел 8.1.3:

8.1.3. Внутренние классы и экземпляры Enclosing

Внутренние классы не могут объявлять статические члены, если только они не являются постоянными переменными (§4.12.4), или возникает ошибка времени компиляции.

И из 4.12.4:

Переменная примитивного типа или типа String, которая является окончательной и инициализирована выражением константы времени компиляции (§15.28), называется постоянной переменной.

EDIT:

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

Ответ 2

Подробнее о предыдущем ответе. Компилятор должен доказать, что это значение является константой. Компилятор Java знает семантику базовых типов (int, float и т.д.) И класс java.lang.String, но не другие классы. Это может понять постоянство первых двух примеров.

Компилятор не понимает, что Integer.valueOf(3) также (эффективно) является константой (фактически не константой, но всегда одинаковой), даже если человек, который знает, как работает класс Integer, знает об этом. Компилятор рассматривает это так, как если бы это было Integer.valueOf(x), которое может измениться. Было бы хорошо, если бы Java предложила аннотацию, такую ​​как @interface Consistent, которая объявила поведение метода стабильным для любых параметров предоставления, например:

В классе Integer: @Consistent public Integer valueOf (int x) {...}

final static Integer intThree = Integer.valueOf(3);//теперь компилируется!

Это указывает, что метод возвращает один и тот же или равный объект для каждого вызова с одинаковыми значениями аргументов. Поскольку аргумент является константным выражением, компилятор может вывести, что результат будет одинаковым/равным во всем использовании и поэтому может рассматриваться как константа. В этом случае Integer возвращает один и тот же объект, но может возвращать другой (но равный) объект для большого количества входных значений lager (т.е. Кэширует значения около 0).

Обратите внимание, что "new" всегда возвращает другой объект. Для нового Object() он всегда является объектом, не равным никакому другому объекту.

Ответ 3

Рассмотрим определение выражения константы времени компиляции из 15.28:

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

  • Литералы примитивного типа и литералов типа String (§3.10.1, §3.10.2, §3.10.3, §3.10.4, §3.10.5)
  • Отбрасывает примитивные типы и приведения типа String (§15.16)
  • Унарные операторы +, -, ~ и ! (но не ++ или --) (§15.15.3, §15.15.4, §15.15.5, §15.15.6)
  • Мультипликативные операторы *, / и % (§15.17)
  • Аддитивные операторы + и - (§15.18)
  • Операторы сдвига <<, >> и >>> (§15.19)
  • Реляционные операторы <, <=, > и >= (но не instanceof) (§15.20)
  • Операторы равенства == и != (§15.21)
  • Побитовые и логические операторы &, ^ и | (§15.22)
  • Условный оператор и оператор && и условный оператор или оператор || (§15.23, §15.24)
  • Тернарный условный оператор ? : (§15.25)
  • Обобщенные выражения (§15.8.5), содержащее выражение - постоянное выражение.
  • Простые имена (§6.5.6.1), которые относятся к постоянным переменным (§4.12.4).
  • Квалифицированные имена (§6.5.6.2) формы TypeName. Идентификатор, который ссылается на константу переменные (§4.12.4).

Следуя определению выражения константы времени компиляции, 4.12.4:

Переменная примитивного типа или типа String, то есть final и инициализированная выражением константы времени компиляции (§15.28), называется постоянной переменной.

Наконец, из 8.1.3:

Внутренние классы не могут объявлять статические члены, если только они не являются постоянными переменными (§4.12.4) или возникает ошибка времени компиляции.