What are streams?
Streams was introduced in Java 1.8 and it had completely changed how we write code. The majority of what I will be discussing will be what I have learnt from watching this talk by Venkat Subramaniam. His talk was what had originally got me into using streams and the concept of lambdas.
Let’s revise
How do we implement a lambda in Java?
Lambdas are simple constructs with very powerful use cases in Java and many other languages. Most commonly, lambdas enable the everyday programmer to reduce their clunky anonymous inner classes into simple one-liners. It can also be used to pass methods around between methods without having to redeclare these method over and over again.
The common components that make up a lambda in Java are:
- A functional interface
- A method that matches the signature of the method in the functional interface
What is a functional interface?
A functional interface is an interface that contains a single method.
|
|
Using a lambda:
Code:
|
|
Output:
|
|
In this example, I created a functional interface (StringOp
), declared an instance of that functional interface (operation
) and gave it’s definition all in one line using a lambda.
The core syntax of the lambda is as such:
(parameters) -> {
actions
}
In cases where there is only 1 parameter, the parantheses can be omitted, as seen in the example, and if the method body a single line, you can also omit the curly braces.
If lambdas did not exist, I would have to declare the method like this:
|
|
These are just simple examples of what lambdas are capable of, there a many more uses for them and you can check out a more comprehensive guide here: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
Starting streams
I will first begin by showing an example of a typical program and then showing the power of streams and how they can be used to simplify your work.
Problem: Write a program to print out all numbers that are multiples of a given number within a given range.
Traditional Solution:
|
|
Streams Solution:
|
|
Output:
2
4
6
8
10
As you can see, both methods produce the same output, however, the latter is a lot neater than the former. Not only is the solution simpler to understand than the solution that introduces loops and if statements, it is a lot easier to read and understand.
Explanation:
The reason why methods like .filter(i -> i % multiple == 0)
works is due to the use of functional interface as mentioned previously. According to the Java documentation on streams, .filter()
receives a Predicate
interface as a parameter.
Represents a predicate (boolean-valued function) of one argument.
This means in order to create a lambda that receives one argument and returns a boolean condition.
Method references
Another unusual syntax you might have noticed is this forEach(System.out::println)
, you might be scratching your head and wondering that this ::
symbol is doing. Well, it is known as a method reference. The core idea with method references would be as Mr. Venkat put it
Since the value is a simple pass over, you can use a method reference.
To illustrate this, let’s see how you would use the .forEach()
method normally:
|
|
Output:
Pizza
Cola
Salad
As you can see, for forEach()
, the menuItem
argument you receive is simply being passed onto the System.out.println
method call, and since no other modification is being made to this menuItem
value, you can use a method reference to System.out.println
to shorten to code.
In this particular instance, since println
is a static method of the System.out
object, the method reference will be a reference to a static method, which means the syntax would simply be having the object name
followed by the ::
symbol and then the target method name
.