Попробуйте с помощью ресурсов и вернуть операторы в 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)
Другие вопросы по тегам