Исключительно безопасное возвращение автозаполняемого объекта - PullRequest
4 голосов
/ 01 апреля 2019

Если вы хотите использовать какой-либо объект AutoClosable, вы должны использовать try-with-resources . Хорошо. Но что, если я хочу написать метод, который возвращает 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?

1 Ответ

3 голосов
/ 01 апреля 2019

Есть несколько подходов.

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

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

Выполнение вокруг идиомы

Простое и лучшее решение.К сожалению, библиотека 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);
     }
}

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

Модифицированный try-with-resource как функция библиотеки, реализованная в виде прокси AutoCloseable в try-with-resource

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

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

Общая реализация returnResource будет выглядеть следующим образом.Хак, потому что try-with-resource не поддерживает такого рода вещи, а библиотека 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 в целом.
...