Есть ли правильный способ закрыть ресурсы, открытые в java stream api (для каждого элемента)?

Есть ли правильный способ открыть ресурс для каждого элемента в коллекции, чем использовать потоковый API, сделать некоторые map(), filter(), peek() и т. д., используя ресурс, а затем закрыть ресурс?

У меня есть что-то вроде этого:

List<String> names =  getAllNames();
names.stream().map(n -> getElementFromName(n))
              .filter(e -> e.someCondition())
              .peek(e -> e.doSomething())
              .filter(e -> e.otherCondition())
              .peek(e -> e.doSomethingElse())
              .filter(e -> e.lastCondition())
              .forEach(e -> e.doTheLastThing());

Это должно работать нормально, за исключением того, что я открываю ресурс (например, соединение с базой данных) в методе getElementFromName. Затем я работаю с этим ресурсом в других методах экземпляра, таких как someCondition или doSomething. И я понятия не имею, как его правильно закрыть.

Я знаю, что использую только промежуточные операции на потоке. Это означает, что все методы оцениваются в операции forEach, и производительность должна быть в порядке, потому что итерация выполняется только один раз.

Но я не могу понять, как закрыть ресурсы, открытые для каждого элемента в метод getElementFromName.

Я могу сохранить список всех элементов, созданных с помощью getElementFromName, и закрыть ресурсы позже. Но я бы просто потратил место впустую, сохраняя все ресурсы живыми. Также будет вторая итерация по списку элементов. Что делает предпочтительнее избегать потокового API в этом случае. Итак, есть ли способ как-то автоматически закрыть ресурс, когда я закончу использовать элемент?

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


person user3913960    schedule 17.04.2019    source источник
comment
после того, как вы извлекли данные из базы данных в getElementFromName, вам все еще нужно подключение к базе данных? Или просто данные у вас уже есть?   -  person Wisthler    schedule 18.04.2019


Ответы (2)


Вы можете использовать flatMap для этого:

.flatMap(n -> {
    YourResource r = getElementFromName(n);
    return Stream.of(r).onClose(r::close);
})

Это предполагает, что ресурс, инкапсулированный возвращаемым элементом, имеет метод close(). Если нет, то код усложняется, но картинка должна быть ясна. Вместо простого возврата одного объекта вы возвращаете поток из одного элемента, onClose выполняет необходимую очистку. Если он может генерировать проверенные исключения, вам также придется добавить обработчик для этого, желательно обернув исключение в непроверенное исключение.

Это зависит от гарантии, предоставленной для flatMap:

Каждый сопоставленный поток closed< /a> после того, как его содержимое было помещено в этот поток.

Но в целом этот код, особенно чрезмерное использование peek, выглядит весьма подозрительно.

person Holger    schedule 18.04.2019
comment
Большое спасибо за ответ. Я просто играл с потоковым API, потому что хотел использовать его, чтобы изучить его. Также я намеренно немного переборщил, чтобы показать суть. - person user3913960; 18.04.2019

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

В этом примере я предполагаю, что getElementFromName возвращает объект типа Element:

List<Element> elements = new ArrayList<>(); // Or whatever kind of list you want
List<String> names =  getAllNames();
names.stream().map(n -> getElementFromName(n))
              .peek(e -> elements.add(e)) // Add the elements to the list
              .filter(e -> e.someCondition())
              .peek(e -> e.doSomething())
              .filter(e -> e.otherCondition())
              .peek(e -> e.doSomethingElse())
              .filter(e -> e.lastCondition())
              .forEach(e -> e.doTheLastThing());

elements.forEach(e -> e.close()); // Close all the elements
person Benjamin Urquhart    schedule 17.04.2019