Working with collections versus working with streams
Everyone working with Java is aware of collections. We use collections in an imperative way: we tell the program how to do what it's supposed to do. Let's take the following example in which we instantiate a collection of 10 integers, from 1 to 10:
List<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < 10; i++) { list.add(i); }
Now, we will create another collection in which we will filter in only the odd numbers:
List<Integer> odds = new ArrayList<Integer>(); for (int val : list) { if (val % 2 == 0) odds.add(val); }
At the end, we want to print the results:
for (int val : odds) { System.out.print(val); }
As you can see, we wrote quite a bit of code to perform three basic operations: to create a collection of numbers, to filter the odd numbers, and then to print the results. Of course, we could do all the operations in only one loop, but what if we could do it without using a loop at all? After all, using a loop means we tell the program how to do its task. From Java 8 onwards, we have been able to use streams to do the same things in a single line of code:
Streams are defined in the java.util.stream package, and are used to manage streams of objects on which functional-style operations can be performed. Streams are the functional correspondent of collections, and provide support for map-reduce operations.
We will further discuss streams and functional programming support in Java in later chapters.