Неинициализированная переменная Java с окончательным любопытством - PullRequest
6 голосов
/ 08 августа 2009

Я пытался придумать неясные тестовые примеры для альтернативной JVM с открытым исходным кодом, с которой я помогаю ( Avian ), когда натолкнулся на интересную часть кода, и я был удивлен, что не компилируем:

public class Test {
    public static int test1() {
        int a;
        try {
            a = 1;
            return a; // this is fine
        } finally {
            return a; // uninitialized value error here
        }
    }
    public static void main(String[] args) {
        int a = test1();
    }
}

Наиболее очевидный путь к коду (единственный, который я вижу) - выполнить a = 1, «попытка» вернуть (в первый раз), а затем выполнить finally, которое на самом деле возвращает , Тем не менее, javac жалуется, что «а», возможно, не было инициализировано:

    Test.java:8: variable a might not have been initialized  
        return a;  
               ^  

Единственное, о чем я могу думать, это может вызвать / разрешить другой путь к коду, если неясное исключение времени выполнения возникло после начала попытки, но до того, как значение 1 было присвоено - что-то похожее на OutOfMemoryError или StackOverflowException, но я не могу вспомнить ни одного случая, когда это могло бы произойти в этом месте кода.

Может кто-нибудь, более знакомый со спецификой стандарта Java, пролить свет на это? Является ли это просто случаем, когда компилятор консервативен - и поэтому отказывается компилировать то, что в противном случае было бы допустимым кодом - или здесь происходит что-то более странное?

Ответы [ 7 ]

10 голосов
/ 08 августа 2009

Может показаться нелогичным, что в строке a = 1 может возникнуть исключение, но может возникнуть ошибка JVM. Таким образом, оставляя переменную неинициализированной. Итак, ошибка компилятора имеет полный смысл. Это та неясная ошибка времени выполнения, о которой вы упоминали. Тем не менее, я бы сказал, что OutOfMemoryError далеко не скрыт и должен, по крайней мере, обдумываться разработчиками. Кроме того, помните, что состояние, которое устанавливает OutOfMemoryError, может произойти в другом потоке, и единственным действием, которое выталкивает объем кучи памяти, использованной после ограничения, является присвоение переменной a.

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

7 голосов
/ 08 августа 2009

Спецификация языка Java требует, чтобы переменная была назначена до ее использования. JLS определяет конкретные правила для этого правила, известного как правила «Определенное назначение». Все компиляторы Java должны придерживаться их.

JLS 16.2.15

V определенно назначается перед блоком finally, если V определенно назначается перед оператором try.

Другими словами, при рассмотрении оператора finally операторы блоков try и catch в назначениях операторов try-catch-finally не учитываются.

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

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

3 голосов
/ 08 августа 2009

Я полагаю, что это только из-за семантики отношений "попробуй поймай наконец". Из спецификации языка Java :

Если выполнение блока try завершает нормально, то, наконец, блок выполнен ...

Если выполнение блока try завершается внезапно из-за броска со значением V ...

Если выполнение блока try завершается внезапно для любого другого причина R, то, наконец, блок выполняется ...

Последний случай представляется наиболее актуальным здесь. Кажется, что блок finally должен быть в состоянии правильно выполняться, если блок try завершается внезапно по ЛЮБОЙ причине. Очевидно, что если блок try закончился до назначения, блок finally не будет действительным. Хотя, как вы сказали, это не особенно вероятно.

2 голосов
/ 01 ноября 2012

ошибки компилятора возникают в условных блоках, если компилятор не уверен, что следующее (успешно) Оператор будет работать как

int i=5;int d;
if(i<10)
{system.out.println(d);}

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

int i;

if(true){}

else
{System.out.println(d);}

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

int i;
if(true)
{System.out.println(d);}
else{}

поскольку блоки try попадают под это правило, они следуют тем же правилам.

2 голосов
/ 08 августа 2009

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

Теперь, если кто-то может указать на соответствующий раздел в Спецификации языка Java ...

1 голос
/ 08 августа 2009

Полагаю, Java-компилятор предполагает наихудший случай - нет никаких гарантий, что что-либо в блоке try будет выполнено по какой-то причине. Так что его жалоба действительна. Переменная может не инициализирована.

0 голосов
/ 08 августа 2009

Компилятор здесь просто консервативный.

...