5

Is there a correct way to open a resource for each element in collection, than use stream api, do some map(), filter(), peek() etc. using the resource and than close the resource?

I have something like this:

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());

This should work fine, except I'm opening a resource (e.g. db connection) in the getElementFromName method. Then I'm working with that resource in the other instance methods like someCondition or doSomething. And I have no idea how to close it correctly.

I know, that I'm using only intermediate operations on the stream. Which means, that all methods are evaluated in the forEach operation and performance should be ok because the iteration is done only once.

But I can't figure out how to close the resources opened for each element in the getElementFromName method.

I can save the list of all elements created by getElementFromName and close the resources later. But I would be just wasting space, keeping all the resources alive. Also there would be second iteration through the list of elements. Which makes avoiding stream api preferable in this case. So is there a way to somehow auto close the resource when I'm done using the element?

Also I know, it can be easily done using foreach lopp, I was just curious if it can be done using stream api.

  • once you extracted the data from the database in getElementFromName, do you still need the database connection? Or just the data you already have? – Wisthler Apr 18 '19 at 06:32

2 Answers2

2

You can use flatMap for that:

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

This assumes that the resource encapsulated by the returned element has the close() method. If not, the code gets more complicated, but the picture should be clear. Instead of just returning a single object, you’re returning a single-element stream whose onClose action does the necessary cleanup. If it can throw checked exceptions, you’ll also have to add a handler for that, preferably wrapping the exception in an unchecked exception.

This is relying on the guaranty made for flatMap:

Each mapped stream is closed after its contents have been placed into this stream.

But generally, this code, especially the overuse of peek, looks highly suspicious.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thank you very much for the answer. I was just playing with the stream api, because I wanted to use it to learn it. Also I overkilled it a little on purpose, to show the point. –  Apr 18 '19 at 19:10
0

You can create a second list to store the elements as you come across them. Unfortunately, that is the only way that I know of to close all of the elements using streams.

In this example, I will assume getElementFromName return an object of type 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
Benjamin Urquhart
  • 1,626
  • 12
  • 18