Lambda and Stream in Java
Lambdas are an anonymous function. That means function with no name.
(param) → body
In java 8 Stream API’s support both lambda expression and anonymous inner class. Both work fine but there is some major difference while compiling.
Anonymous inner class.
public class Demo {
public static void main(String[] args) throws IOException {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.stream()
.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer e) {
System.out.println("e = " + e);
}
});
}
}
If you compile this code this will create extra class per each anonymous inner class. If the main class has 100 anonymous inner class it will create each class for byte code.
This will take extra time to load and these anonymous inner classes in JVM and also the size of the jar file will also be increased. We can easily replace these anonymous classes with lambda. There are only two important things in the above inner class. one is an input parameter and another is the body of the function. The above code can simply write as:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.stream().forEach(e -> System.out.println("e = " + e));
One important property about lambda is it is stateless. Instead of creating a new class JVM will use invoke dynamics to call these lambdas.
Closure
The closure is a function that carries an immutable state. Unlike, lambda closure can carry state. This means that it can access variables, not in its parameter list.
Example:
int multiplier = 2;
numbers.stream()
.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer e) {
System.out.println("multiplier = " + multiplier);
}
});
Here multiplier variable is outside of the function accept but it can still be accessible from that function.
The Closure can hold the final or effectively final variable. Effectively final variables are the variables whose value is never changed after it is initialized. It will give a compile-time error if we try to change the multiplier variable in the above code after it is initialized.
Method References
We can use method reference for those function which simply receive data and pass to that function.
Example:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.stream()
.map(Integer::intValue) //method reference
.mapToInt(e -> e.intValue()) //lambda expression
.sum();
A method reference can be used in both the instance method and static method. DO not confuse the syntactic structure it will be the same for both.
Example:
numbers.stream()
.map(Integer::intValue) //instance method
.map(Objects::toString) //static method
.forEach(System.out::println); //instance method
Stream Methods
Map (n input → n output)
Map simply maps input data to respective output data.
Example:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);numbers.stream()
.map(Objects::toString) //integer maps to string
.forEach(System.out::println);
Filter (n input → n or less output)
The filter simply takes n input and based on filtering condition produces n or less output.
Example:
String firstName = "ram";
String middleName = "";
String lastName = "sharma";String fullName = Stream.of(firstName, middleName, lastName)
.filter(Objects::nonNull)
.filter(e -> !e.isEmpty())
.collect(Collectors.joining(" "));
FlatMap(one to many transformations)
FlatMap firstly transforms one stream to many elements then flattening the resulting elements into a new stream.
A simple CSV reader can be created as:
Path path = Paths.get("/home/shankar/Desktop/input_sample.csv");
Files.lines(path)
.flatMap(line -> Stream.of(line.split(",")))
.forEach(System.out::println);
Peek( debugging tool )
peek can be used as the debugging method. It will take consumers as input. The difference between peek and forEach is peek is an intermediate operation whereas forEach is a terminal operation. Peek should be only used for debugging purposes.
example:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);numbers.stream()
.filter(e -> e % 2 == 0) //even numbers
.peek(System.out::println) //check whether it filtered correctly
.forEach(System.out::println);
Reduce (Reduction )
Perform reduction on stream. It is a terminal operation. (not lazy)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);Integer total = numbers.stream()
.reduce(0, (acc, e) -> acc + e);
System.out.println("Average = " + total / numbers.size());
This reduction operation takes two parameter
— identity: initial value of the reduction and the default result if there are no elements in the stream. for addition, it should be 0, for multiplication it can be 1.
— Accumulator: takes a partial result of reduction and next element of the stream.
forEach
It is also a terminal operation. So this method will be executed as soon as it is called. (not lazy). It will accept the consumer method. you can use this operation when you are required to perform some input consuming task.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);numbers.stream()
.filter(e -> e % 2 == 0)
.forEach(System.out::println);//terminal, not lazy
forEachOrder
It is also a terminal operation. The main difference between forEach and forEachOrder is it will perform this final operation based on the order of the original stream. The forEachOrder method doesn’t guarantee order when the stream is an unordered collection. For example, List is order collection so it will guarantee the order in this terminal operation whereas for the set is unorder collection so it will not guarantee orderly execution of the stream.
List<Integer> numbers = Arrays.asList(8, 1, 3, 4, 5, 6, 7, 2, 9);
numbers.stream()
.parallel()
.filter(e -> e % 2 ==0)
.forEachOrdered(System.out::print); //output 8462
Don’t use forEachOrder until unless you need to process items in the original order.