Почему изменение возвращаемой переменной в блоке finally не изменяет возвращаемое значение?

У меня есть простой класс Java, как показано ниже:

public class Test {

    private String s;

    public String foo() {
        try {
            s = "dev";
            return s;
        } 
        finally {
            s = "override variable s";
            System.out.println("Entry in finally Block");  
        }
    }

    public static void main(String[] xyz) {
        Test obj = new Test();
        System.out.println(obj.foo());
    }
}

И вывод этого кода таков:

Entry in finally Block
dev  

Почему s не переопределяется в блоке finally, но управляет печатным выходом?

Ответ 1

Блок try завершает выполнение оператора return и значение s во время выполнения оператора return - это значение, возвращаемое методом. Тот факт, что предложение finally позже изменяет значение s (после завершения инструкции return), не возвращает (в этой точке) возвращаемое значение.

Обратите внимание, что приведенное выше относится к изменениям самого значения s в блоке finally, а не к объекту, который ссылается s. Если s была ссылкой на изменяемый объект (который String не указан), а содержимое объекта было изменено в блоке finally, то эти изменения будут видны в возвращаемом значении.

Подробные правила того, как все это работает, можно найти в Раздел 14.20.2 Спецификации Java Language. Обратите внимание, что выполнение оператора return считается резким завершением блока try (начало раздела "Если выполнение блока try завершается внезапно по любой другой причине R..." ). См. Раздел 14.17 JLS, почему оператор return представляет собой резкое завершение блока.

В качестве дополнительной информации: если и блок try, и блок finally оператора try-finally прерывается из-за операторов return, тогда применяются следующие правила из §14.20.2:

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

  • Если блок finally завершается нормально, то инструкция try завершается внезапно по причине R.
  • Если блок finally неожиданно завершает работу по причине S, то оператор try завершается внезапно по причине S (и причина R отбрасывается).

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

Ответ 2

Поскольку возвращаемое значение помещается в стек перед окончанием вызова.

Ответ 3

Если мы заглянем внутрь байт-кода, мы заметим, что JDK сделал значительную оптимизацию, а метод foo() выглядит следующим образом:

String tmp = null;
try {
    s = "dev"
    tmp = s;
    s = "override variable s";
    return tmp;
} catch (RuntimeException e){
    s = "override variable s";
    throw e;
}

И байт-код:

0:  ldc #7;         //loading String "dev"
2:  putstatic   #8; //storing it to a static variable
5:  getstatic   #8; //loading "dev" from a static variable
8:  astore_0        //storing "dev" to a temp variable
9:  ldc #9;         //loading String "override variable s"
11: putstatic   #8; //setting a static variable
14: aload_0         //loading a temp avariable
15: areturn         //returning it
16: astore_1
17: ldc #9;         //loading String "override variable s"
19: putstatic   #8; //setting a static variable
22: aload_1
23: athrow

java сохранила строку "dev" от изменения перед возвратом. На самом деле здесь нет окончательного блока вообще.

Ответ 4

Здесь есть 2 вещи:

  • Строки неизменяемы. Когда вы задаете s для переопределения переменной s, вы устанавливаете s для ссылки на строчную строку, не изменяя встроенный буфер char объекта s, чтобы изменить на "переопределить переменную s".
  • Вы помещаете ссылку на s в стеке, чтобы вернуться к вызывающему коду. Впоследствии (когда выполняется блок finally), изменение ссылки не должно делать ничего для возвращаемого значения уже в стеке.

Ответ 5

Я немного изменил код, чтобы доказать точку Теда.

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

public class Test {

public String s;

public String foo() {

    try {
        s = "dev";
        return s;
    } finally {
        s = "override variable s";
        System.out.println("Entry in finally Block");

    }
}

public static void main(String[] xyz) {
    Test obj = new Test();
    System.out.println(obj.foo());
    System.out.println(obj.s);
}
}

Вывод:

Entry in finally Block 
dev 
override variable s

Ответ 6

С технической точки зрения, return в блоке try не будет игнорироваться, если будет определен блок finally, только если этот блок finally также включает return.

Это сомнительное дизайнерское решение, которое, вероятно, было ошибкой в ​​ретроспективе (так же, как ссылки были обнуляемыми/изменяемыми по умолчанию и, по некоторым данным, проверенными исключениями). Во многом это поведение в точности согласуется с разговорной концепцией того, что означает finally - "независимо от того, что происходит заранее в блоке try, всегда запускайте этот код". Следовательно, если вы возвращаете true из блока finally, общий эффект должен всегда быть return s, no?

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

Ответ 7

Попробуйте: Если вы хотите напечатать значение переопределения s.

finally {
    s = "override variable s";    
    System.out.println("Entry in finally Block");
    return s;
}