Попробуйте с помощью ресурсов и вернуть операторы в Java
Мне интересно, если размещение оператора return внутри блока try-with-resources предотвращает автоматическое закрытие ресурса.
try(Connection conn = ...) {
return conn.createStatement().execute("...");
}
Если я напишу что-то подобное, Соединение будет закрыто? В документации Oracle указано, что:
Оператор try-with-resources обеспечивает закрытие каждого ресурса в конце оператора.
Что произойдет, если конец оператора никогда не будет достигнут из-за оператора возврата?
4 ответа
Основываясь на руководстве Oracle, "[ресурс] будет закрыт независимо от того, завершается ли оператор try нормально или внезапно". Определяет abruptly
как исключение.
Возвращаясь внутрь try
пример внезапного завершения, как определено в JLS 14.1.
Ресурс будет закрыт автоматически (даже с return
заявление), так как он реализует AutoCloseable
интерфейс. Вот пример, который выводит "успешно закрыт":
public class Main {
public static void main(String[] args) {
try (Foobar foobar = new Foobar()) {
return;
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Foobar implements AutoCloseable {
@Override
public void close() throws Exception {
System.out.println("closed successfully");
}
}
В
AutoCloseable
Интерфейс может запутать порядок выполнения кода на первый взгляд. Давайте рассмотрим это на примере:
public class Main {
// An expensive resource which requires opening / closing
private static class Resource implements AutoCloseable {
public Resource() {
System.out.println("open");
}
@Override public void close() throws Exception {
System.out.println("close");
}
}
// find me a number!
private static int findNumber() {
// open the resource
try(Resource resource = new Resource()) {
// do some business logic (usually involving the resource) and return answer
return 2 + 2;
} catch(Exception e) {
// resource encountered a problem
throw new IllegalStateException(e);
}
}
public static void main(String[] args) {
System.out.println(findNumber());
}
}
Приведенный выше код пытается открыть некоторые
Resource
и выполнить некоторую бизнес-логику с использованием ресурса (в данном случае просто арифметические операции). Запуск кода напечатает:
open
close
4
Следовательно
Resource
закрывается перед выходом из блока try-with-resource. Чтобы было понятно, что именно происходит, давайте реорганизуем
findNumber()
метод.
private static int findNumber() {
// open the resource
int number;
try(Resource resource = new Resource()) {
// do some business logic and return answer
number = 2 + 2;
} catch(Exception e) {
// resource encountered a problem
throw new IllegalStateException(e);
}
return number;
}
Концептуально это то, что происходит под капотом, когда
return
помещается в блок try-with-resource. В
return
операция перемещается после блока try-with-resource, чтобы разрешить
AutoCloseable
объект закрыть перед возвращением.
Следовательно, мы можем заключить, что
return
операция внутри блока try-with-resource - это просто синтаксический сахар, и вам не нужно беспокоиться о возврате до того, как
AutoCloseable
закрылся.
Хорошие ответы уже были опубликованы. Я просто использую другой подход, так как это похоже на возможность погрузиться в некоторые детали, которые могут когда-нибудь пригодиться, а именно попытаться ответить на вопрос, прочитав какой-нибудь байт-код.
Есть несколько сценариев - посмотреть на
- исключение в блоке
- исключение при закрытии при выходе на блок
- исключение, когда ресурс во время обработки более раннего исключения
- return в блоке try выполняется до return.
Первый сценарий обычно лучше всего подходит для использования в java. Мы можем попытаться понять остальные три сценария, взглянув на байт-код. Последний сценарий отвечает на ваш вопрос.
Разбираем байт-код для
main
метод ниже
import java.io.*;
class TryWith {
public static void main(String[] args) {
try(PrintStream ps = System.out) {
ps.println("Hey Hey");
return;
}
}
}
Давайте рассмотрим его по частям (некоторые детали опущены)
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: astore_1
0: получить статическое поле
System.out
.
3: сохранить поле в
LocalVariableTable
(lvt) в слоте 1.
Просмотрев lvt, мы можем подтвердить, что первый слот принадлежит
java.io.PrintStream
и у него есть имя
LocalVariableTable:
Start Length Slot Name Signature
4 35 1 ps Ljava/io/PrintStream;
0 39 0 args [Ljava/lang/String;
4: aload_1
5: ldc #3 // String Hey Hey
7: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
4: Загрузить (
aload_1
)
5: Загрузить константу (
ldc
), из постоянного пула.
7: Вызвать метод строки печати, это потребляет и
hey hey
из стека операндов.
10: aload_1
11: ifnull 18
14: aload_1
15: invokevirtual #5 // Method java/io/PrintStream.close:()V
18: return
10 - 11: загрузить в стек операндов. проверить, есть ли, и если да, перейти к
18
и из функции.
14–18: загрузить, вызвать и .
Вышеизложенное представляет особый интерес, поскольку предполагает, что
try-with
Блок будет работать, если
Auto-Closeable
ресурс есть и нет
throw
исключение. Конечно, даже если бы это сработало, это было бы спорным, если только к ресурсу не обращались в блоке. Любой доступ приведет к NPE.
Вышеприведенное также является нормальным потоком, что происходит даже в случае исключения? Давайте посмотрим на таблицу исключений
Это говорит нам о том, что любое исключение типа
java.lang.Throwable
между байт-кодом 4-10 обрабатывается цель 19. Аналогично для строк 24-28 в строке 31.
19: astore_2
20: aload_1
21: ifnull 37
24: aload_1
25: invokevirtual #5 // Method java/io/PrintStream.close:()V
28: goto 37
19: Сохранить исключение в локальной переменной
2
.
20 - 25: это тот же шаблон, который мы видели ранее, вызывается, только если
ps
не 28: инструкция перехода к
37
37: aload_2
38: athrow
37: загрузить объект, хранящийся в таблице локальных переменных в позиции 2, ранее мы сохраняли исключение в этой позиции.
38: бросить исключение
Однако как насчет случая исключения, возникающего во время, когда
close
выполнялся из-за более раннего исключения. Давайте повторим таблицу исключений
Exception table:
from to target type
4 10 19 Class java/lang/Throwable
24 28 31 Class java/lang/Throwable
Это вторая строка таблицы исключений, давайте посмотрим на соответствующий байтовый код цели 31.
31: astore_3
32: aload_2
33: aload_3
34: invokevirtual #7 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
37: aload_2
38: athrow
31: вторичное исключение сохраняется в локальной переменной в слоте 3.
32: перезагрузите исходное исключение из слота 3.
33-34: добавьте вторичное исключение в качестве подавленного исключения к исходному исключению.
37-38: генерировать новое исключение, мы рассмотрели эти строки ранее.
Возвращаясь к нашему соображению, перечисленному в начале
- исключение при закрытии во время выхода на блок.
** возникает исключение и блок выходит - исключение, когда
closing
ресурс во время обработки более раннего исключения.
** Подавленное исключение добавляется к исходному исключению, и создается исходное исключение. вtry
блокировать выходыabruptly
- return в блоке try, закрывается перед возвратом.
** закрытие выполняется доreturn
в пробном блоке
Пересматривая интересные сценарии
auto-closeable
ресурс
null
с которым мы столкнулись в байтовом коде, мы можем проверить это с помощью
import java.io.*;
class TryWithAnother {
public static void main(String[] args) {
try(PrintStream ps = null) {
System.out.println("Hey Hey");
return;
}
}
}
Неудивительно, что мы получаем вывод
Hey Hey
на консоли и не исключение.
Последнее, но очень важное, о чем следует помнить, это то, что этот байт-код является совместимой реализацией JLS. Этот подход очень удобен для определения того, что влечет за собой ваше фактическое выполнение, могут быть и другие совместимые альтернативы - в этой ситуации я не могу придумать ни одной. Однако, имея это в виду, этот ответ не будет полным без указания моего
javac
версия
openjdk 11.0.9.1 2020-11-04
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.9.1+1)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.9.1+1, mixed mode)