Должен ли я использовать try-with-resource в flatMap для потока на основе ввода-вывода? - PullRequest
0 голосов
/ 22 февраля 2019

A Stream представляет собой AutoCloseable и, если он основан на вводе / выводе, должен использоваться в блоке try-with-resource.Как насчет промежуточных потоков на основе ввода / вывода, которые вставляются через flatMap()?Пример:

try (var foos = foos()) {
   return foos.flatMap(Foo::bars).toArray(Bar[]::new);
}

против

try (var foos = foos()) {
  return foos.flatMap(foo -> {
    try (var bars = foo.bars()) {
      return bars;
    }
  }).toArray(Bar[]::new);
}

Документация flatMap() гласит:

Каждый сопоставленный поток закрывается после помещения его содержимого вэтот поток.

Ну, это счастливый путь.Что если между ними произошло исключение?Тогда этот поток останется незамеченным и потенциально утечка ресурсов?Должен ли я всегда использовать try-with-resource также для промежуточных потоков?

Ответы [ 2 ]

0 голосов
/ 22 февраля 2019

Нет смысла в такой конструкции, как

return foos.flatMap(foo -> {
    try (var bars = foo.bars()) {
        return bars;
    }
}).toArray(Bar[]::new);

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

Фактически, код функции не может гарантировать, что закрытие произойдет в соответствующем месте, которое находится за пределами функции.Это, безусловно, причина, по которой разработчики API решили, что вам это не нужно, и реализация Stream позаботится о них.

Это также относится к исключительному случаю.Stream по-прежнему гарантирует, что поток будет закрыт, как только функция вернет его в Stream:

try {
    IntStream.range(1, 3)
        .flatMap(i -> {
            System.out.println("creating "+i);
            return IntStream.range('a', 'a'+i)
                    .peek(j -> {
                        System.out.println("processing sub "+i+" - "+(char)j);
                        if(j=='b') throw new IllegalStateException();
                    })
                    .onClose(() -> System.out.println("closing "+i));
        })
        .forEach(i -> System.out.println("consuming "+(char)i));
} catch(IllegalStateException ex) {
    System.out.println("caught "+ex);
}
creating 1
processing sub 1 - a
consuming a
closing 1
creating 2
processing sub 2 - a
consuming a
processing sub 2 - b
closing 2
caught java.lang.IllegalStateException

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

Для такой операции потока, как .flatMap(Foo::bars) или .flatMap(foo -> foo.bars()), можно предположить, что однажды bars() успешно создан иВозвращенный поток, он будет точно передан вызывающей стороне и должным образом закрыт.

Другой сценарий - это функции отображения, которые выполняют операции после создания потока, которые могут завершиться неудачей, например,

.flatMap(foo -> {
    Stream<Type> s = foo.bar();
    anotherOperation(); // Stream is not closed if this throws
    return s;
})
* 1023.* В этом случае было бы необходимо обеспечить закрытие в исключительном случае, но только в исключительном случае:
.flatMap(foo -> {
    Stream<Type> s = foo.bar();
    try {
        anotherOperation();
    } catch(Throwable t) {
        try(s) { throw t; } // close and do addSuppressed if follow-up error
    }
    return s;
})

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

0 голосов
/ 22 февраля 2019

В потоке или нет, вы должны закрыть ресурсы ввода-вывода в соответствующем месте.
Метод flatMap() является общим потоковым методом, поэтому он не знает о ресурсах ввода-вывода, которые вы открыли внутри него.
НоПочему flatMap() будет вести себя иначе, чем любой метод, который манипулирует ресурсами ввода-вывода?Например, если вы управляете вводом-выводом в map(), вы можете получить ту же проблему (без освобождения ресурса), если произойдет исключение.
Закрытие потока (как в flatMap()) не заставит его освободить все ресурсы, открытые впотоковая операция.
Некоторые методы делают это, например File.lines(Path).Но если вы откроете себе некоторые ресурсы в flatMap(), закрытие этих ресурсов не будет происходить автоматически при закрытии потока.
Например, здесь обработка flatMap не закрывает открытые FileInputStream:

 ...
 .stream()
 .flatMap(foo -> {
    try {
       FileInputStream fileInputStream = new FileInputStream("..."));                                  
       //...
     }
     catch (IOException e) {
         // handle
     }

 })

Вы должны закрыть его явно:

 ...
 .stream()
 .flatMap(foo -> {
     try (FileInputStream fileInputStream = new FileInputStream("...")){
         //...
     } catch (IOException e) {
         // handle
     }
    // return
 })

Так что да, если операторы, используемые внутри flatMap() или любого метода, манипулируют некоторыми ресурсами ввода-вывода, вы хотите закрыть их в любом случае,окружив его оператором try-with-resources, чтобы сделать их свободными.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...