Является ли попытка с ресурсом небезопасной при объявлении нескольких эффективных конечных ресурсов?

Начиная с Java 9 мы можем эффективно использовать конечные переменные в try-with-resources.

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

    public static void main(String[] args) {
        Resource1 r1 = new Resource1();
        Resource2 r2 = new Resource2(); // exception will be thrown
        try (r1; r2) {
            System.out.println("TryWithResources.main() try");
        } catch (Exception e) {
            System.out.println("TryWithResources.main() catch");
        }
    }
    
    static class Resource1 implements AutoCloseable {
        @Override
        public void close() throws Exception {
            System.out.println("TryWithResources.Resource1.close()");
        }
    }
    
    static class Resource2 implements AutoCloseable {
        public Resource2() {
            throw new RuntimeException();
        }
        @Override
        public void close() throws Exception {
            System.out.println("TryWithResources.Resource2.close()");
        }
    }

Когда я запускаю этот пример, единственный вывод, который я получаю, — это RuntimeException, что означает, что Resource1 не был закрыт. Этого и следовало ожидать, так как он не был инициализирован в try-with-resources.

Но это ожидаемый результат, или я что-то упускаю?

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

Может кто-нибудь подтвердить, так ли это на самом деле? И, если да, то зачем нам использовать этот синтаксис с несколькими ресурсами и идти на такой риск?


person RinaldoDev    schedule 09.02.2021    source источник


Ответы (2)


Я думаю, вы предполагаете, что инициализация происходит в операторе try. Исключение генерируется конструктором до достижения try-with-resources. Другими словами, строка try (r1; r2) { сама по себе не инициализирует ресурсы, она просто ссылается на них как на переменные. Это отличается от инициализации ресурса в try блоках:

try (r1; Resource2 r2 = new Resource2()) { // this causes r1 to close if r2 throws an exception

Сказав это, вы правы в том, что новый синтаксис (доступ к конечной переменной) дает гибкость за счет возможности не закрывать ранее созданные ресурсы (как показано в вашем случае). Лично у меня никогда не было причин использовать этот новый синтаксис. Я не могу придумать веской причины для того, чтобы не создавать ресурс в операторе try, в конце концов, нет смысла использовать ресурс после его закрытия.

person M A    schedule 09.02.2021
comment
Извините, я думаю, что я написал вопрос, возможно, у меня сложилось впечатление, что я это предполагал. Я не был. Я только хотел убедиться, что не должно произойти какой-то магии, и что по какой-то причине это не так. Ваш ответ проясняет это, и это тот же вывод, что и у меня. Спасибо. :) - person RinaldoDev; 09.02.2021
comment
@RinaldoDev Не беспокойтесь, на самом деле, перечитав ваш вопрос, я понял, что вы пытались поднять в своем вопросе, и поэтому я добавил второй абзац в свой ответ. - person M A; 10.02.2021
comment
Если вам нужен практический пример использования новой функции, рассмотрите этот ответ. Метод возвращает Stream, поддерживаемый ресурсами JDBC. Таким образом, он не может использовать обычный try(…), так как это безоговорочно закроет ресурсы до того, как поток будет возвращен вызывающей стороне. Только в ошибочном случае все ресурсы должны быть безопасно закрыты перед доставкой исключения вызывающей стороне. В случае успеха Runnable должен быть зарегистрирован в экземпляре Stream, чтобы закрыть уже существующие ресурсы, когда поток был закрыт вызывающей стороной. - person Holger; 11.02.2021
comment
Код этого ответа использует UncheckedCloseable для представления уже открытых ресурсов и обрабатывает их единообразно для обоих случаев, немедленно закрываясь при ошибке или регистрируясь как Runnable в Stream в случае успеха. Обратите внимание на шаблон try(Resource dummy = existing) внутри этого типа; так это делалось до Java 9, теперь с Java 9+ мы можем написать try(existing), что актуально только для таких особых случаев. Во всех остальных случаях следует использовать try(Resource name = creation}expression), как и раньше. - person Holger; 11.02.2021

Чтобы механизм try-with-resources заработал, вы должны поместить свой код инициализации в try. Ваша инициализация, кажется, происходит на 1 строку раньше. Если бы вы сделали:

  public static void main(String[] args) {
    try (Resource1 r1 = new Resource1(); Resource2 r2 = new Resource2()) {
      System.out.println("TryWithResources.main() try");
    } catch (Exception e) {
      System.out.println("TryWithResources.main() catch");
    }
  }

который будет печатать:

TryWithResources.Resource1.close()
TryWithResources.main() catch

Таким образом, ваш код следует читать так: Я хочу автоматически закрыть r1 и r2, но предпочитаю инициализировать их самостоятельно

И мой вариант следует читать так: Я хочу автоматически закрыть r1 и r2, а также я хочу инициализировать его как часть блока try

person gokareless    schedule 09.02.2021