Разница в поведении: инициализированный конечный статический член "null" и инициализированная конечная локальная переменная "null"

Я столкнулся с поведением, о котором я раньше не знал, в последующем коде.

Рассмотрим случай 1 st:

public static void main(String[] args) {    
    final String str = null;
    System.out.println(str.length());  // Compiler Warning: NullPointerAccess
}

Как и ожидалось, компилятор показывает, что следующее предупреждение на str имеет значение null - Null-указатель: переменная str может быть только нулевой в этом месте.

Теперь, когда я переместил эту переменную, статическое конечное поле, инициализированное нулем:

class Demo {
    static final String str = null;

    public static void main(String[] args) {
        System.out.println(str.length());  // No Compiler Warning
    }
}

Теперь компилятор не показывает никаких предупреждений. AFAIK, компилятор должен знать, что str является окончательным, не изменит его значение в любой точке кода. И учитывая, что это null, определенно приведет к NullPointerException позже, что он делает.

Хотя, компилятор успешно предупреждает меня об этом в первом случае, почему он не может идентифицировать это во втором случае. Почему это изменение поведения? Поведение такое же, если я изменяю поле static в поле instance и получаю доступ к нему с помощью экземпляра Demo.

Я думал, что это поведение, возможно, было указано в JLS, поэтому я просмотрел раздел "Определенное задание", но не нашел ничего, связанного с этой проблемой. Может ли кто-нибудь объяснить изменения в поведении? Я ищу какую-то сильную точку с некоторой ссылкой на JLS, если это возможно?

Кроме того, почему компилятор показывает мне только предупреждение в первую очередь, так как я думаю по той же причине, о которой я говорил выше, вызов метода определенно бросает NPE во время выполнения, так как поле не может быть изменено? Почему это не показало мне ошибку компилятора? Я ожидаю слишком многого от компилятора, поскольку, как представляется, совершенно очевидно, что результат выполнения str.length() не может быть чем-то вроде NPE?


Простите, что раньше не было:

Я использую Eclipse Juno, на Ubuntu 12.04 с OpenJDK 7.

Ответы

Ответ 1

Ничего себе! Оказалось, это была особая проблема затмения. Когда я скомпилировал код, используя:

javac -Xlint:all Demo.java

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

В Windows → Preferences → Java → Compiler → Errors/Warnings, в разделе Null Analysis, я могу изменить способ обращения к Null Pointer Access с помощью Eclipse Compiler. Я могу установить его - Игнорировать, Ошибка или Предупреждение.

И теперь это кажется совершенно глупым вопросом. Мне стыдно.: (

Ответ 2

Я не уверен в 100%, но во втором случае, когда у вас есть окончательное поле против локальной переменной, можно назначить этому финальному полю какое-то значение прямо в статическом (или блок экземпляра в зависимости от того, является ли переменная статической или нет) блок инициализации:

class Demo {
...
static {
 str = "some text";
}
...
}

поэтому компилятор не дает вам предупреждения.

Ответ 3

public static void main(String[] args) {    
    final String str = null;
    System.out.println(str.length());  // Compiler Warning: NullPointerAccess
}

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

class Demo {
    static final String str = null;

    public static void main(String[] args) {
        System.out.println(str.length());  // No Compiler Warning
    }
}

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

Но почему здесь нет предупреждений?

Вы могли бы инициализировать переменную экземпляра в конструкторе. Или вы можете вызвать метод setter на нем, прежде чем вызывать на нем функцию length(). Таким образом, он ускользает от анализа потока (компилятор не уверен, будет ли переменная экземпляра быть нулевой в этой точке или нет, но в компиляции 1-го случая обязательно, что локальная переменная всегда будет равна нулю).