Использование интерфейсов AutoClosable внутри Stream API

Сегодня я попытался реорганизовать этот код, который читает идентификаторы из файлов в каталоге,

Set<Long> ids = new HashSet<>();
for (String fileName : fileSystem.list("my-directory")) {
    InputStream stream = fileSystem.openInputStream(fileName);
    BufferedReader br = new BufferedReader(new InputStreamReader(stream));
    String line;
    while ((line = br.readLine()) != null) {
        ids.add(Long.valueOf(line.trim()));
    }
    br.close();
}

с использованием потока api

Set<Long> ids = fileSystem.list("my-directory").stream()
    .map(fileName -> fileSystem::openInputStream)
    .map(is -> new BufferedReader(new InputStreamReader(is)))
    .flatMap(BufferedReader::lines)
    .map(String::trim)
    .map(Long::valueOf)
    .collect(Collectors.toSet());

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

Любые идеи?

upd: Файловая система в примере - HDFS, Files#lines и подобные методы не могут быть использованы.

Ответы

Ответ 1

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

.flatMap(reader -> reader.lines().onClose(() -> close(reader)))

Где close(AutoClosable) обрабатывает исключение IOException.

В качестве доказательства концепции был протестирован следующий код и вывод:

import java.util.stream.Stream;

class Test {
    public static void main(String[] args) {
        Stream.of(1, 2, 3).flatMap(i ->
                Stream.of(i, i * 2).onClose(() ->
                        System.out.println("Closed!")
                )
        ).forEach(System.out::println);
    }
}

1
2
Closed!
2
4
Closed!
3
6
Closed!

Ответ 2

Почему не проще, через Files.lines:

try (Stream<String> s = Files.lines(Paths.get("yourpath" + fileName))) {
    s.map(String::trim)
      .map(Long::valueOf)
      .collect(Collectors.toSet());
}

Ответ 3

Я не тестировал фактический код, но, возможно, что-то в этом роде?

Set<Long> ids = fileSystem.list("my-directory").stream()
.map(fileName -> fileSystem::openInputStream)
.flatMap(is -> {
    try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
      return is.lines().map(String::trim).map(Long::valueOf);
    }
 })
.collect(Collectors.toSet());

Конечно, не так красиво, как ваш, но я считаю, что это самое близкое, что позволяет закрыть его.