3

Suppose I have a method used by multiple classes in my application. This method takes a Stream as a parameter and applies a terminal operation forEach in order to write the data to a file, something like the following:

public File writeStreamToTempFile(Stream stream) {
    stream.forEach(item -> {
        //some code to write item to a file
    }
}

There are multiple callers of this method, and in some of those methods I need to transform the data, let's say using map function, like the following:

public void exportAnimalsData() {
    Stream<Animal> animalStream = //fetch data from a DB
    animals.filter(a -> a.type.equals("dog"))
           .map(a -> //Do something useful to transform the Dogs);

    writeStreamToTempFile(animalStream);
}

Not all the callers of the writeStreamToTempFile method need to perform additional operations on the stream.

So my question is:

Is it a bad practice to apply operations to a stream in different methods?

I read somewhere that Stream should never be the return type of a method (the caller does not know if that method already consumes the stream or not), does it also apply for parameters of a method?

Should I just apply all the operations needed in the same method or is it ok to append intermediate operations to the same stream in a different method?

ikos23
  • 4,879
  • 10
  • 41
  • 60
Luis Miguel
  • 495
  • 8
  • 22
  • 3
    *Stream should never be the return type of a method* sounds like a bad policy, one frequently violated by the JDK. It should be obvious that a method returning a stream wouldn't have consumed it. – shmosel Nov 21 '18 at 21:05
  • 1
    Look for “Methods … that return Stream” in https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/class-use/Stream.html – Holger Nov 22 '18 at 09:38

2 Answers2

8

i read somewhere that Stream should never be the return type of a method

Not sure where you read that a Stream should never be the return type of a method. (Can you update your question with a link?).

On the contrary, Brian Goetz, one of the designers of the Stream API, clearly thinks otherwise:

https://stackoverflow.com/a/24679745/340088

(the caller does not know if that method already consume the stream or not)

If the caller is getting a Stream it is implicitly understood that the stream is usable. If you return a consumed Stream it is like returning a closed Socket or a closed InputStream. While possible syntactically, it is just bad coding. Doesn't mean you shouldn't return a Socket or an InputStream from a method just because some bad coder occasionally returns one in a bad state.

On the contrary, monadic style objects are intended to be returned. Stream, Optional, CompletableFuture, and all the functional interfaces (Function, Consumer, Operator, etc.) are intended to be returned so that more functional-style operations can be attached to them, without actually performing the function on the spot.

The example you posted, to me, is perfectly reasonable. You are sort of decorating your Stream with additional operations to the pipeline. You can have logic which decides which operations are added to the pipeline, and there is no reason why not to encapsulate them properly in small methods.

Furthermore, if the stream is very large carrying hundreds of thousands of elements, if you returned a collection, you would just be creating a huge collection in memory, incurring the cost to add them all, only to then stream them again to write them to the file. Doesn't make much sense does it?

jbx
  • 21,365
  • 18
  • 90
  • 144
  • Trying to find the video where i heard that it is not recommended to return a Stream from a method, will update the question once i find it. BTW great answer, thanks. – Luis Miguel Nov 21 '18 at 21:12
  • 3
    Would be interested to see it. But its probably wrong (or contextually different). Rest assured its perfectly fine to return `Stream` (or any other functional type for that matter). Just do your code readable and sensibly organised. – jbx Nov 21 '18 at 21:18
0

should i just apply all the operations needed in the same method or is it ok to append intermediate operations to the same stream in different method?

Technically it is not necessarily the same stream/object. For example invoking an operation such as map() on the stream will create another stream.

In fact I think that while you didn't consume the stream with a terminal operation I don't see why it would be an issue to work on it to create or get the expected stream and then to pass to the method that expects a Stream.

davidxxx
  • 125,838
  • 23
  • 214
  • 215