Неинициализированная переменная Java с окончательно любопытством

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

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, "попытаться" вернуть a (в первый раз), а затем выполнить наконец, который на самом деле возвращает a. Однако javac жалуется, что "a", возможно, не было инициализировано:

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

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

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

7 ответов


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

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


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

JLS 16.2.15:

V определенно назначается до того, как окончательно блок iff V определенно назначается до оператора try.

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

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

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


Я считаю, что это просто из-за семантики отношений try-catch-finally. От Спецификация Языка Java:

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

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

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

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


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

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


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

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{}

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


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


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