1 Creating streams
Before I started this topic, there were some preliminaries that I had to be familiar with. You know those times when you try to get a stream of SomeType, but instead you get a stream of List<SomeType> instead? The following examples will clarify them:
1.1 int[] array stream
Arrays.stream(int[]) - returns IntStream
Arrays.stream(Integer[]) - returns Stream<Integer>
Stream.of(int...) - returns Stream<Integer>
Stream.of(List<Integer>) - returns Stream<List<Integer>>; you probably don't want this
1.2 Enum.values() stream
Stream.of(enum.values()) - returns Stream; you probably want this, unlike integer version
Equivalently, you can use Arrays.stream(enum.values()).
1.3 Collection/List stream
... and if you just invoke .stream() on Collection (including List, Set) objects, you get the expected behaviour.
1.4 Infinite stream
Stream.generate() takes a Supplier functional interface (method that looks like ()->return T), and invokes it every time more elements are needed for the stream. Note that stream is lazily loaded, meaning that element is only retrieved at the execution of terminal operation/pipe.
2 Using stream
Here is the main principle to decide how/whether to use stream.
Does it improve readability?
Also, there are some things that stream cannot replace traditional/conventional codes for:
- Throwing exception, break, continue within pipelines
- Variable from earlier pipelines cannot be used by later pipelines (workaround for this is to apply inverse function to the parameter to get the before-pipeline values or use a datastructure that holds both)
2.1 Standard usecase of stream - classification into map<key, list>
Here's a nice example:
Methods such as 'groupingBy', 'filter', collect', 'forEach' make code clearer than the alternative:
You can see that the main benefit of stream is its readability. However, as streams grow in length and use more niche part of the Stream API, readability will reduce and might not be apt for environment where other workers are not used to Stream (functional) style.
2.2 Standard use of stream - making a cartesian product
Note this makes use of infinite stream (section 1.4, randomNames()) and enum stream (section 1.2). Role of flatMap (part of Streams API) is to take list of lists, and put it into a list.
Nested for loop could also have done the job nicely. Both traditional approach and functional approach will have very similar look, and it will depend on your preference as to whether you will use it. Which one Is more readable for your team? The guiding principle when it's difficult to decide is to implement it in both ways and judge.
2.3 Small aside: Files and Paths
Note both classes have plural names, so by naming convention they are static utility classes.
Files API greatly aids use of stream. Files aids use of stream. Paths provide simple way to create a URI object (of type Path). Paths essentially has only 'get' method.
Path object can be used for Scanner and also for Files.list or Files.lines.
2.3.1 Print files in current directory
Paths.get(URI) takes a path. "" will be current directory. Files.list(path) returns Stream<Path> for paths of all files in that directory.
2.3.2 Print lines of a file without using scanner
Imagine that a textfile called 'studentlist' is placed as shown (outside of src) - that's the current directory too.
Here's how to print lines in that file: