Безопасный для исключений возврат объекта Autoclosable

Если вы хотите использовать какой-либо объект AutoClosable, вы должны использовать попытка с ресурсами. Ok. Но что, если я хочу написать метод, который возвращает AutoClosable? После того, как вы создали или откуда-то получили объект AutoCloseable, вы должны закрыть его в случае исключения, например так:

    public static AutoCloseable methodReturningAutocloseable() {
        AutoCloseable autoCloseable = ... // create some AutoClosable
        try {
            ... // some work
        }
        catch (Throwable exception) {
            autoCloseable.close();
            throw exception;
        }
        return autoCloseable;
    }

Если вы не напишите блок try/catch, вы утечете ресурс, который содержит этот объект autoCloseable, в случае исключения в строке // some work. Но этого try/catch недостаточно, потому что autoCloseable.close() тоже может генерировать исключение (по задумке). Таким образом, приведенный выше код преобразуется в

    public static AutoCloseable methodReturningAutocloseable() {
        AutoCloseable autoCloseable = ... // create some autoclosable
        try {
            ... // some work
        }
        catch (Throwable exception) {
            try {
                autoCloseable.close();
            }
            catch (Throwable exceptionInClose) {
                exception.addSuppressed(exceptionInClose);
                throw exception;
            }
            throw exception;
        }
        return autoCloseable;
    }

Это много шаблонов. Есть ли лучший способ сделать это в java?

Вопрос будет заключаться в том, почему вы хотите вернуть autoclosable?, поскольку он уже был бы закрыт в операторе return.

Kiskae 01.04.2019 14:38

@Kiskae, почему он закрывается в операторе return в приведенном выше примере кода?

a.khakh 01.04.2019 15:00
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
2
762
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Есть ряд приближенных.

  • Используйте Идиомы Execute Around. Переформулируйте интерфейс, чтобы облегчить реализацию клиента и устранить проблему.
  • Игнорируйте проблему. Звучит глупо, но обычно это происходит при обертывании декораторами потока ввода-вывода.
  • Оберните прокси AutoCloseable и поместите его в свою попытку с ресурсом.
  • Напишите эквивалент использования try-with-resource try-catch и try-finally. (Я бы не стал - противно.)
  • Напишите модифицированную попытку с ресурсом как библиотечную функцию. Этот код станет клиентом Execute Around.
  • Напишите обработку исключений в старом школьном стиле с помощью try-catch и try-finally.

Редактировать: Я решил вернуться к ответу, добавив несколько примеров кода для развлечения.

Идиомы Execute Around

Простое и лучшее решение. К сожалению, в библиотеке Java он используется нечасто (AccessController.doPrivileged — большое исключение), и соглашения не установлены. Как всегда, проверенные исключения Java без поддержки функций усложняют ситуацию. Мы не можем использовать java.util.function и должны изобретать собственные функциональные интерфейсы.

// Like Consumer, but with an exception.
interface Use<R, EXC extends Exception> {
    void use(R resource) throws EXC;
}

public static void withThing(String name, Use<InputStream,IOException> use) throws IOException {
     try (InputStream in = new FileInputStream(name)) {
         use.use(in);
     }
}

Красиво и просто. Не нужно беспокоиться о том, что клиентский код испортит обработку ресурсов, поскольку он этого не делает. Хороший.

Модифицированная попытка с ресурсом как библиотечная функция, реализованная как прокси AutoCloseable в попытке с ресурсом

Это будет некрасиво. Нам нужно передать получение, выпуск и инициализацию как лямбда-выражения. Создание ресурса непосредственно в этом методе открывает небольшое окно, в котором неожиданное исключение может привести к утечке.

public static InputStream newThing(String name) throws IOException {
    return returnResource(
        () -> new FileInputStream(name),
        InputStream::close,
        in -> {
            int ignore = in.read(); // some work
        }
    );
}

Общая реализация returnResource будет выглядеть так, как показано ниже. Хак, потому что попытка с ресурсом не поддерживает такие вещи, а библиотека Java плохо поддерживает проверенные исключения. Примечание ограничено одним исключением (вы можете использовать непроверенное исключение для непроверенных исключений).

interface Acquire<R, EXC extends Exception> {
    R acquire() throws EXC;
}
// Effectively the same as Use, but different.
interface Release<R, EXC extends Exception> {
    void release(R resource) throws EXC;
}

public static <R, EXC extends Exception> R returnResource(
    Acquire<R, EXC> acquire, Release<R, EXC> release, Use<R, EXC> initialize
) throws EXC {
    try (var adapter = new AutoCloseable() { // anonymous classes still define type
        private R resource = acquire.acquire();
        R get() {
            return resource;
        }
        void success() {
            resource = null;;
        }
        public void close() throws EXC {
           if (resource != null) {
               release.release(resource);
           }
        }
    }) {
        R resource = adapter.get();
        initialize.use(resource);
        adapter.success();
        return resource;
    }
}

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

public static InputStream newThing(String name) throws IOException {
    return returnResource(
        name,
        FileInputStream::new,
        InputStream::close,
        in -> {
            int ignore = in.read(); // some work
        }
    );
}

// Like Function, but with a more descriptive name for a functional interface.
interface AcquireFrom<T, R, EXC extends Exception> {
    R acquire(T t) throws EXC;
}

public static <T, R, EXC extends Exception> R returnResource(
    T t, AcquireFrom<T, R, EXC> acquire, Release<R, EXC> release, Use<R, EXC> initialize
 ) throws EXC {
     return returnResource(() -> acquire.acquire(t), release, initialize);
 }

Итак, в целом, следующие вещи являются болью:

  • Передача права собственности на ресурсы. Держите это локально.
  • java.util.function не поддерживает отмеченные исключения.
  • Библиотека Java не поддерживает Execute Around.
  • AutoCloseable::close объявляя, что он выдает Exception вместо того, чтобы быть параметром типа типа.
  • Проверяемые исключения без типов суммы.
  • Синтаксис языка Java в целом.

Другие вопросы по теме