Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
43 views57 pages

Java 8 Lambda Expressions

The document outlines the new features introduced in Java 8, including Lambda expressions, Functional interfaces, Stream API, and the new Date and Time API. It explains the importance of these features in supporting functional programming paradigms and provides examples of core functional interfaces such as Consumer, Supplier, Function, and Predicate. Additionally, it discusses the use of method references and the benefits of using Optional to handle null values more effectively.

Uploaded by

Lo Se R
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
43 views57 pages

Java 8 Lambda Expressions

The document outlines the new features introduced in Java 8, including Lambda expressions, Functional interfaces, Stream API, and the new Date and Time API. It explains the importance of these features in supporting functional programming paradigms and provides examples of core functional interfaces such as Consumer, Supplier, Function, and Predicate. Additionally, it discusses the use of method references and the benefits of using Optional to handle null values more effectively.

Uploaded by

Lo Se R
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 57

Contents

1 What New Features Were Added in Java 8?............................................................................................2


2 Important Topics........................................................................................................................................2
3 What is and why Lambda?........................................................................................................................4
4 Functional interfaces?...............................................................................................................................5
4.1 Core Functional Interfaces.................................................................................................................5
4.2 What is Consumer Interface?.............................................................................................................5
4.3 Functions in Consumer Interface.......................................................................................................6
4.3.1 accept().......................................................................................................................................6
4.3.2 andThen()...................................................................................................................................7
5 What is predicate Interface?......................................................................................................................8
5.1 Java Predicate example.....................................................................................................................8
5.2 Java Predicate with lambda...............................................................................................................9
5.3 Java Predicate example II................................................................................................................10
5.4 Java IntPredicate.............................................................................................................................12
5.5 Composing predicates.....................................................................................................................12
5.6 Negating predicates.........................................................................................................................14
5.7 Java predicate as method parameter..............................................................................................15
6 3. Method references?............................................................................................................................20
6.1 Introduction......................................................................................................................................20
6.2 Java 8 Method Reference...............................................................................................................21
6.3 Static method reference..................................................................................................................22
6.4 Instance method reference of an object of a particular type..........................................................25
6.5 Instance method reference of an existing object............................................................................29
6.6 Constructor method reference........................................................................................................31
6.7 Conclusion.......................................................................................................................................35
7 Java Spliterator Features........................................................................................................................35
7.1 2. Java Spliterator Methods.............................................................................................................36
7.2 3. Java Spliterator Example.............................................................................................................36
7.3 External iteration..............................................................................................................................42
7.4 Internal iteration...............................................................................................................................43
8 1. Java Stream vs. Collection..................................................................................................................45
9 2. Different ways to create streams.........................................................................................................46
10 3. Convert streams to collections.........................................................................................................48
11 4. Core stream operations...................................................................................................................49
11.2 5. Stream short-circuit operations....................................................................................................53
12 6. Parallelism in Java Steam...............................................................................................................54

1 What New Features Were Added in Java 8?

This is the most asked interview question on Java 8. Many of the java developers do not work on java 8.
Below are the java 8 features:

1. Functional Interface: Each functional interface has a single abstract method, called the functional
method, implementation can be provided using the lambda expressions.
2. Lambda Expressions: It is a feature derived from the functional programming. It is a function that does
not belong to any class.
3. Optional: Instead of using null values Optional class is used for representing Optional values.
4. Stream api
5. Spliterator
6. Method References
7. New Date and Time API.

2 Important Topics
Java 8 introduced several key features that support functional programming paradigms. Here are the main
topics related to functional programming in Java 8:
1. Lambda Expressions:
o Lambda expressions provide a clear and concise way to represent instances of functional
interfaces (interfaces with a single abstract method).
o Syntax: (parameters) -> expression or (parameters) -> { statements; }.

2. Functional Interfaces:

2
o A functional interface is an interface with exactly one abstract method.

o Java 8 introduced the @FunctionalInterface annotation to ensure that an interface meets the
criteria of a functional interface.
o Examples: Runnable, Comparator, and custom interfaces like MyFunctionalInterface.

3. Method References:
o Method references provide a way to refer to methods without invoking them.

o They are often used to make lambda expressions more readable.

o Types:

 Reference to a static method: ClassName::staticMethodName


 Reference to an instance method of a particular object: object::instanceMethodName
 Reference to an instance method of an arbitrary object of a particular
type: ClassName::instanceMethodName
 Reference to a constructor: ClassName::new
4. Stream API:
o The Stream API provides a functional approach to process collections of objects.

o It supports operations like filter, map, reduce, collect, and more.

o Streams can be created from collections, arrays, or I/O channels.

o Example:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");


List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());

5. Default Methods:
o Default methods allow interfaces to have methods with implementation without affecting the
classes that implement the interface.
o This feature was introduced to support backward compatibility while evolving the API.

o Example:

interface MyInterface {
default void myDefaultMethod() {
System.out.println("Default method implementation");
}
}

6. Optional Class:

3
o The Optional class is a container object which may or may not contain a non-null value.

o It is used to avoid NullPointerException and to handle null values more gracefully.

o Example:

Optional<String> optional = Optional.ofNullable(getString());


optional.ifPresent(System.out::println);

7. New Date and Time API (java.time package):


o Java 8 introduced a new date and time API to address the shortcomings of the
old java.util.Date and java.util.Calendar.
o Key classes: LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Duration, Period.

LocalDate today = LocalDate.now();


LocalDate tomorrow = today.plusDays(1);

8. Parallel Streams:
o Parallel streams enable parallel processing of data, which can lead to performance
improvements on multi-core processors.
o Example:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");


List<String> filteredNames = names.parallelStream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());

9. CompletableFuture:
o CompletableFuture is used for asynchronous programming and provides a way to handle
future results in a non-blocking manner.
o It supports chaining and combining multiple asynchronous operations.

o Example:

CompletableFuture.supplyAsync(() -> "Hello")


.thenApply(s -> s + " World")
.thenAccept(System.out::println);

These features collectively enhance Java's ability to support functional programming styles, making
it easier to write more concise, readable, and maintainable code.

4
3 What is and why Lambda?

According to Oracle docs,

Lambda expressions are the method without name i.e Anonymous method. In other words, Lambda
expression is a function that can be passed around and referenced as an object.

It is replacement of anonymous classes. In case of anonymous classes, a new .class file gets compiled /
created, while in case with using Lambda expressions no extra file gets created.

A.
1. Enables functional programming.
2. Readable and con size code.
3. Easier-to-use APIs and library.
4. Enables support for parallel processing.

4 Functional interfaces?

A Functional Interface is an Interface which allows only one Abstract method within the Interface scope.
There are some predefined functional interfaces in Java like Predicate, consumer, supplier etc. The return
type of a Lambda function (introduced in JDK 1.8) is an also functional interface.

Java 8 introduced several built-in functional interfaces in the java.util.function package to support functional
programming. These interfaces represent common function types and can be used with lambda
expressions, method references, and streams. Below are the main functional interfaces categorized by their
purpose:

4.1 Core Functional Interfaces

In Java, the java.util.function package contains several core functional interfaces that represent
common function types. These interfaces are used extensively with lambda expressions and method
references. Here are the most important ones:

4.1.1 1. Consumer<T>

 Abstract Method: void accept(T t)


 Purpose: Takes an input of type T and performs an operation (no return value).
 Variants:

5
o BiConsumer<T, U> (2 inputs)
o DoubleConsumer, IntConsumer, LongConsumer (primitive versions)
4.1.2 2. Supplier<T>

 Abstract Method: T get()


 Purpose: Supplies a value of type T (no input, only output).
 Variants:
o BooleanSupplier, DoubleSupplier, IntSupplier, LongSupplier (primitive
versions)
4.1.3 3. Function<T, R>

 Abstract Method: R apply(T t)


 Purpose: Takes an input of type T and returns a result of type R.
 Variants:
o BiFunction<T, U, R> (2 inputs)
o UnaryOperator<T> (extends Function<T, T> – same input & output type)
o BinaryOperator<T> (extends BiFunction<T, T, T> – two inputs, same output type)
o DoubleFunction<R>, IntFunction<R>, LongFunction<R> (primitive input versions)
o ToDoubleFunction<T>, ToIntFunction<T>, ToLongFunction<T> (primitive output
versions)
4.1.4 4. Predicate<T>

 Abstract Method: boolean test(T t)


 Purpose: Takes an input and returns a boolean (used for filtering/conditions).
 Variants:
o BiPredicate<T, U> (2 inputs)
o DoublePredicate, IntPredicate, LongPredicate (primitive versions)
4.1.5 5. Runnable

 Abstract Method: void run()


 Purpose: Represents a task that takes no input and returns nothing.
4.1.6 6. Comparator<T>

 Abstract Method: int compare(T o1, T o2)


 Purpose: Used for comparing two objects (commonly used in sorting).

Primitive Specializations

To avoid autoboxing overhead, Java provides specialized versions for primitives:


 IntConsumer, LongConsumer, DoubleConsumer

6
 IntSupplier, LongSupplier, DoubleSupplier
 IntFunction, LongFunction, DoubleFunction
 ToIntFunction, ToLongFunction, ToDoubleFunction
 IntPredicate, LongPredicate, DoublePredicate
4.1.7 Examples

// Consumer
Consumer<String> print = s -> System.out.println(s);

// Supplier
Supplier<Double> random = Math::random;

// Function
Function<String, Integer> length = String::length;

// Predicate
Predicate<Integer> isEven = n -> n % 2 == 0;

// Runnable
Runnable task = () -> System.out.println("Running!");

These interfaces are foundational for Java's functional programming features (lambdas, streams, etc.).

_____________________________
4.2 What is Consumer Interface?

The Consumer Interface is a part of the java.util.function package which has been introduced since Java
8, to implement functional programming in Java. It represents a function which takes in one argument and
produces a result. However these kind of functions don’t return any value.
Hence this functional interface which takes in one generic namely:-
T: denotes the type of the input argument to the operation
The lambda expression assigned to an object of Consumer type is used to define its accept() which
eventually applies the given operation on its argument. Consumers are useful when it not needed to return
any value as they are expected to operate via side-effects.
4.2.1 Functions in Consumer Interface

The Consumer interface consists of the following two functions:


4.2.2 accept()

This method accepts one value and performs the operation on the given argument

7
// Java Program to demonstrate
// Consumer's accept() method

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;

public class Main {


public static void main(String args[]) {
// Consumer to display a number
Consumer<Integer> display = a -> System.out.println(a);

// Implement display using accept()


display.accept(10);

Consumer<List<String>> modify = list -> {


for (int i = 0; i < list.size(); i++) {
list.set(i, list.get(i) + "_suffix");
}
};

List<String> list = Arrays.asList("hi", "ki");


modify.accept(list);

Consumer<List<String>> dispList = l -> l.forEach(System.out::println);

dispList.accept(list);
}
}

Output:
10

hi_suffix
ki_suffix
4.2.3 andThen()

It returns a composed Comsumer wherein the parameterized Consumer will be executed after the first one.
If evaluation of either function throws an error, it is relayed to the caller of the composed operation.
Note: The function being passed as the argument should be of type Consumer.
Syntax:

default Consumer <T>


andThen(Consumer<? super T> after)

Parameters: This method accepts a parameter after which is the Consumer to be applied after the current
one.
Return Value: This method returns a composed Consumer that first applies the current Consumer first and
then the after operation.

8
Exception: This method throws NullPointerException if the after operation is null.
Below is the code to illustrate andThen() method:
Program 1:
public static void main(String[] args) {

StringBuilder sb = new StringBuilder("Hello");

Consumer<StringBuilder> consumer1 = s -> s.append("_con1");


Consumer<StringBuilder> consumer2 = s -> s.append("_con2");
Consumer<StringBuilder> consumer3 = s -> s.append("_con3");
Consumer<StringBuilder> consumer4 = s -> s.append("_con4");
Consumer<StringBuilder> consumer5 = s -> s.append("_con5");

Consumer<StringBuilder> printConsumer = System.out::println;

consumer1.andThen(consumer2).andThen(consumer3).andThen(consumer4).andThen(consumer5).andThen(printCon
sumer).accept(sb);

Program 2:

// Java Program to demonstrate


// Consumer's andThen() method

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;

public class Main {


public static void main(String args[])
{

// Consumer to multiply 2 to every integer of a list


Consumer<List<Integer> > modify = list ->
{
for (int i = 0; i < list.size(); i++)
list.set(i, 2 * list.get(i));
};

// Consumer to display a list of integers


Consumer<List<Integer> >
dispList = list -> list.stream().forEach(a -> System.out.print(a + " "));

List<Integer> list = new ArrayList<Integer>();


list.add(2);
list.add(1);
list.add(3);

9
// using addThen()
modify.andThen(dispList).accept(list);
;
}
}

Output:
426

______________________________

Example 3:

import java.util.function.Consumer;

public class OrderProcessor {


public static void main(String[] args) {
// Define consumers for each processing step
Consumer<Order> validateOrder = order -> {
if (order.getAmount() <= 0) {
throw new IllegalArgumentException("Invalid order amount!");
}
System.out.println("✅ Validated order: " + order.getId());
};

Consumer<Order> applyDiscount = order -> {


if (order.getCustomer().isPremium()) {
order.setAmount(order.getAmount() * 0.9); // 10% discount
System.out.println("🎉 Applied 10% discount (new amount: $" + order.getAmount() + ")");
}
};

Consumer<Order> notifyCustomer = order -> {


System.out.println("📧 Notified customer: " + order.getCustomer().getEmail());
};

Consumer<Order> logOrder = order -> {


System.out.println("📝 Logged order #" + order.getId() + " for $" + order.getAmount());
};

// Chain all consumers in sequence


Consumer<Order> orderProcessingPipeline = validateOrder
.andThen(applyDiscount)
.andThen(notifyCustomer)
.andThen(logOrder);

// Test the pipeline


Customer premiumCustomer = new Customer("[email protected]", true);
Order order = new Order("ORD-123", 100.0, premiumCustomer);

orderProcessingPipeline.accept(order);
}
}

// Supporting classes
class Order {
private String id;
private double amount;

10
private Customer customer;

public Order(String id, double amount, Customer customer) {


this.id = id;
this.amount = amount;
this.customer = customer;
}

// Getters and setters


public String getId() { return id; }
public double getAmount() { return amount; }
public void setAmount(double amount) { this.amount = amount; }
public Customer getCustomer() { return customer; }
}

class Customer {
private String email;
private boolean isPremium;

public Customer(String email, boolean isPremium) {


this.email = email;
this.isPremium = isPremium;
}

// Getters
public String getEmail() { return email; }
public boolean isPremium() { return isPremium; }
}

Output:

✅ Validated order: ORD-123


🎉 Applied 10% discount (new amount: $90.0)
📧 Notified customer: [email protected]
📝 Logged order #ORD-123 for $90.0

11
4.3 What is predicate Interface?

Predicate in general meaning is a statement about something that is either true or false. In programming,
predicates represent single argument functions that return a boolean value.

Predicates in Java are implemented with interfaces. Predicate<T> is a generic functional interface
representing a single argument function that returns a boolean value. It is located in
the java.util.function package. It contains a test(T t) method that evaluates the predicate on the given
argument.

In Java we do not have standalone functions. Furthermore, methods are not first-class citizens. (They
cannot be added to collections or passed to methods as parameters.) Therefore, we define interfaces and
create objects from these interfaces. Such objects can be then passed to methods such as Iterables.filter().
With Java lambdas it is much easier to work with predicates.

4.3.1 Java Predicate example

The following example creates a simple Java Predicate.


com/zetcode/JavaPredicateEx.java
package com.zetcode;

import java.util.List;
import java.util.function.Predicate;

class BiggerThanFive<E> implements Predicate<Integer> {

@Override
public boolean test(Integer v) {

Integer five = 5;

return v > five;


}
}

public class JavaPredicateEx {

public static void main(String[] args) {

List<Integer> nums = List.of(2, 3, 1, 5, 6, 7, 8, 9, 12);

BiggerThanFive<Integer> btf = new BiggerThanFive<>();


nums.stream().filter(btf).forEach(System.out::println);
}
}

In the example, the predicate is used to filter integers.

class BiggerThanFive<E> implements Predicate<Integer> {

@Override
public boolean test(Integer v) {

12
Integer five = 5;

return v > five;


}
}

This is a Java class implementing the Predicate<Integer> interface.


Its test() method returns true for values bigger than five.
List<Integer> nums = List.of(2, 3, 1, 5, 6, 7, 8, 9, 12);
We have a list of integer values.
BiggerThanFive<Integer> btf = new BiggerThanFive<>();
A BiggerThanFive is instantiated.
nums.stream().filter(btf).forEach(System.out::println);
The predicate object is passed to the filter() method to get all values from the list that are bigger than five.
6
7
8
9
12
This is the output.
4.3.2 Java Predicate with lambda

Java lambda expression simplifies the creation of Java Predicates.

com/zetcode/JavaPredicateEx2.java
package com.zetcode;

import java.util.List;
import java.util.function.Predicate;

public class JavaPredicateEx2 {

public static void main(String[] args) {

List<Integer> nums = List.of(2, 3, 1, 5, 6, 7, 8, 9, 12);

Predicate<Integer> btf = n -> n > 5;

nums.stream().filter(btf).forEach(System.out::println);
}
}

The example filters integer values; this time we use Java lambda expression, which makes the code much
shorter.
Predicate<Integer> btf = n -> n > 5;
This is a one-liner that creates the predicate.

13
4.3.3 Java Predicate example II

The next example uses a predicate with two conditions.


com/zetcode/Country.java
package com.zetcode;

public class Country {

private String name;


private int population;

public Country(String name, int population) {


this.name = name;
this.population = population;
}

public String getName() {


return name;
}

public void setName(String name) {


this.name = name;
}

public int getPopulation() {


return population;
}

public void setPopulation(int population) {


this.population = population;
}

@Override
public String toString() {
return "Country{" + "name=" + name +
", population=" + population + '}';
}
}

We have a Country class; it has name and population attributes.


com/zetcode/JavaPredicateEx3.java
package com.zetcode;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class JavaPredicateEx3 {

public static void main(String[] args) {

List<Country> countries = new ArrayList<>();

countries.add(new Country("Iran", 80840713));


countries.add(new Country("Hungary", 9845000));
countries.add(new Country("Poland", 38485000));

14
countries.add(new Country("India", 1342512000));
countries.add(new Country("Latvia", 1978000));
countries.add(new Country("Vietnam", 95261000));
countries.add(new Country("Sweden", 9967000));
countries.add(new Country("Iceland", 337600));
countries.add(new Country("Israel", 8622000));

Predicate<Country> p1 = c -> c.getName().startsWith("I") &&


c.getPopulation() > 10000000;

countries.stream().filter(p1).forEach(System.out::println);
}
}

In the example, we create a list of countries. We filter the list by the country name and population.

Predicate<Country> p1 = c -> c.getName().startsWith("I") &&


c.getPopulation() > 10000000;

The predicate returns true for countries that start with 'I' and their population is over ten million.
Country{name=Iran, population=80840713}
Country{name=India, population=1342512000}
Two countries from the list fulfill the conditions: Iran and India.
4.3.4 Java IntPredicate

IntPredicate represents a predicate of one int-valued argument. This is the int-consuming primitive type
specialization of Predicate<E>.

com/zetcode/IntPredicateEx.java
package com.zetcode;

import java.util.Arrays;
import java.util.function.IntPredicate;

public class IntPredicateEx {

public static void main(String[] args) {

int[] nums = { 2, 3, 1, 5, 6, 7, 8, 9, 12 };

IntPredicate p = n -> n > 5;

Arrays.stream(nums).filter(p).forEach(System.out::println);
}
}

The example filters an array of int values with filter() and IntPredicate.

int nums[] = { 2, 3, 1, 5, 6, 7, 8, 9, 12 };

We define an array of integers.

IntPredicate p = n -> n > 5;

An IntPredicate is created; it returns true for int values bigger than five.

15
Arrays.stream(nums).filter(p).forEach(System.out::println);
We create a stream from the array and filter the elemetnts. The filter() method receives the predicate as a
parameter.
4.3.5 Composing predicates

With and() and or() methods we can compose predicates in Java.

com/zetcode/JavaPredicateCompose.java
package com.zetcode;

import java.util.Arrays;
import java.util.function.IntPredicate;

public class JavaPredicateCompose {

public static void main(String[] args) {

int[] nums = {2, 3, 1, 5, 6, 7, 8, 9, 12};

IntPredicate p1 = n -> n > 3;


IntPredicate p2 = n -> n < 9;

Arrays.stream(nums).filter(p1.and(p2)).forEach(System.out::println);

System.out.println("**********");

IntPredicate p3 = n -> n == 6;
IntPredicate p4 = n -> n == 9;

Arrays.stream(nums).filter(p3.or(p4)).forEach(System.out::println);
}
}

The example filters data using composition of IntPredicates.

IntPredicate p1 = n -> n > 3;


IntPredicate p2 = n -> n < 9;

Arrays.stream(nums).filter(p1.and(p2)).forEach(System.out::println);
We combine two predicates with the and() method; we get integers that are bigger than three and smaller
than nine.

IntPredicate p3 = n -> n == 6;
IntPredicate p4 = n -> n == 9;

Arrays.stream(nums).filter(p3.or(p4)).forEach(System.out::println);
With the or() method, we get values that are equal either to six or nine.
5
6
7
8

16
**********
6
9
This is the output.
4.3.6 Negating predicates

The negate() method returns a predicate that represents the logical negation of the given predicate.

com/zetcode/JavaPredicateNegate.java
package com.zetcode;

import java.util.Arrays;
import java.util.function.IntPredicate;

public class JavaPredicateNegate {

public static void main(String[] args) {

int[] nums = {2, 3, 1, 5, 6, 7, 8, 9, 12};

IntPredicate p = n -> n > 5;

Arrays.stream(nums).filter(p).forEach(System.out::println);

System.out.println("**********");

Arrays.stream(nums).filter(p.negate()).forEach(System.out::println);
}
}

The example demonstrates the usage of the negate() method.

IntPredicate p = n -> n > 5;

We have a predicate that returns true for values bigger than five.

Arrays.stream(nums).filter(p).forEach(System.out::println);

We filter all integers that are bigger than five.

Arrays.stream(nums).filter(p.negate()).forEach(System.out::println);

With the negate() method, we get the opposite: values lower or equal to four.
6
7
8
9
12
**********
2
3
1
5

17
This is the output of the example.
4.3.7 Java predicate as method parameter

Predicates can be passed as method parameters.

com/zetcode/PredicateMethodParam.java
package com.zetcode;

import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class PredicateMethodParam {

public static void main(String args[]) {

List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7,


8, 9, 10, 11, 12);

List<Integer> all = eval(list, n -> true);


System.out.println(all);

List<Integer> evenValues = eval(list, n -> n % 2 == 0);


System.out.println(evenValues);

List<Integer> greaterThanSix = eval(list, n -> n > 6);


System.out.println(greaterThanSix);
}

private static List<Integer> eval(List<Integer> values,


Predicate<Integer> predicate) {
return values.stream().filter(predicate)
.collect(Collectors.toList());
}
}

_______________________________

_______________

4.4 An Example of Lambda with comparator

package com.anurag.example.lambda_exp;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;

interface Condition {
boolean test(Person p);
}

18
class Person {
String name;
String lname;
int age;

public Person(String name, String lname, int age) {


super();
this.name = name;
this.lname = lname;
this.age = age;
}

//Getters, setters and toString()


}

public class LambdaExample {

public static void main(String[] args) {

List<Person> persons = Arrays.asList(new Person("krishna", "patle", 2),


new Person("Tulsi", "patle", 2),
new Person("Ankita", "patle", 25),
new Person("Anurag", "patle", 30),
new Person("Krishna", "patle", 2));

//Step 1: sort
//Collections.sort(persons, (Person o1, Person o2) ->
o1.getName().compareTo(o2.getName()));
//or
Collections.sort(persons, (o1, o2) -> o1.getName().compareTo(o2.getName()));

//Step 2: Create a method which prints all the elements in the list.
//printAll(persons);
//or
printConditionally(persons, p -> true);

//Step 3: Print all the names that start with letter A


//printConditionally(persons, (p) -> p.getName().startsWith("A"));
//or
printConditionally(persons, p -> p.getName().startsWith("A"));

//print name starts with k.


printConditionally(persons, p -> p.getName().startsWith("K"));

rivate static void printConditionally(List<Person> persons, Condition cond) {


for (Person person : persons) {
if (cond.test(person)) {
System.out.println(person);
}
}
}

19
private static void printAll(List<Person> persons) {
for (Person person : persons) {
System.out.println(person);
}

}
}

# If you want to change argument (which is interface 'Condition') of function printConditionally(..)

then

Say you want to use 'Predicate' interface of class 'java.util.function' pkg.

private static void printConditionally(List<Person> persons, Predicate<Person> pred) {


for (Person person : persons) {
if (pred.test(person)) {
System.out.println(person);
}
}
}

And you won't get any error.

# You can use any interface from the pkg java.util.function, like Consumer interface

private static void printConditionally(List<Person> persons, Predicate<Person> pred,


Consumer<Person> consumer) {
for (Person person : persons) {
if (pred.test(person)) {
consumer.accept(person);
}
}
}

Then call will be like,


//print name starts with k.
printConditionally(persons, p -> p.getName().startsWith("K"), p ->
System.out.println(p.getName()));

i.e. p -> System.out.println(p.getName()) is an implementation of accept() method.


And p -> p.getName().startsWith("K") is an implementation of test() function.

_________________________________________
4.5 Handling exception in lambda expression

20
- using java.util.function BiConsumer interface

public static void main(String[] args) {


int[] someNumbers = {1, 2, 3, 4};
int key = 2;

process(someNumbers, key, (v, k) -> System.out.println(v/k));


}

private static void process(int[] someNumbers, int key, BiConsumer<Integer, Integer>


consumer) {
for (int i: someNumbers) {
consumer.accept(i, key);
}

$ if in above example if 'key' == 0 then it will throw divide by zero exception.


$ How will you handle the exception?

public class LambdaExample {

public static void main(String[] args) {


int[] someNumbers = {1, 2, 3, 4};
int key = 0;

process(someNumbers, key, wrapperLamda((t, u) -> System.out.println(t/u)));


}

private static void process(int[] someNumbers, int key, BiConsumer<Integer, Integer>


consumer) {
for (int i: someNumbers) {
consumer.accept(i, key++);
}

private static BiConsumer<Integer, Integer> wrapperLamda(BiConsumer<Integer, Integer>


consumer) {
return (t, u) -> {
System.out.println("Executing from wrapper..");

try {
consumer.accept(t, u);
} catch (ArithmeticException e) {
System.out.println("Exception caught.");
}

};
}

21
5 Method references?

5.1 Introduction

In Java, we can use references to objects, either by creating new objects:

List list = new ArrayList();


store(new ArrayList());

Or by using existing objects:

List list2 = list;


isFull(list2);

But what about a reference to a method?

If we only use a method of an object in another method, we still have to pass the full object as an
argument. Wouldn't it be more practical to just pass the method as an argument? For example:

isFull(list.size);

In Java 8, thanks to lambda expressions, we can do something like this. We can use methods as if
they were objects, or primitive values.

5.2 Java 8 Method Reference

A method reference is the shorthand syntax for a lambda expression that executes just ONE method.
Here's the general syntax of a method reference:

Object :: methodName

We know that we can use lambda expressions instead of using an anonymous class. But sometimes,
the lambda expression is really just a call to some method, for example:

Consumer<String> c = s -> System.out.println(s);

22
To make the code clearer, you can turn that lambda expression into a method reference:

Consumer<String> c = System.out::println;

In a method reference, you place the object (or class) that contains the method before the :: operator
and the name of the method after it without arguments.

But you may be thinking:

 How is this clearer?


 What will happen to the arguments?
 How can this be a valid expression?
 I don't understand how to construct a valid method reference...
First of all, a method reference can't be used for any method. They can only be used to replace a
single-method lambda expression.

So to use a method reference, you first need a lambda expression with one method. And to use a
lambda expression, you first need a functional interface, an interface with just one abstract method.

In other words:

Instead of using
AN ANONYMOUS CLASS
you can use
A LAMBDA EXPRESSION
And if this just calls one method, you can use
A METHOD REFERENCE

There are four types of method references:

 A method reference to a static method.


 A method reference to an instance method of an object of a particular type.
 A method reference to an instance method of an existing object.
 A method reference to a constructor.

23
Let's begin by explaining the most natural case, a static method.

5.3 Static method reference

In this case, we have a lambda expression like the one below:

(args) -> Class.staticMethod(args)

This can be turned into the following method reference:

Class::staticMethod

Notice that between a static method and a static method reference, instead of the . operator, we use
the :: operator, and that we don't pass arguments to the method reference.

In general, we don't have to pass arguments to method references. However, arguments are treated
depending on the type of method reference.

In this case, any arguments (if any) taken by the method are passed automatically behind the curtains.

Where ever we can pass a lambda expression that just calls a static method, we can use a method
reference. For example, assuming this class:

class Numbers {
public static boolean isMoreThanFifty(int n1, int n2) {
return (n1 + n2) > 50;
}
public static List<Integer> findNumbers(
List<Integer> l, BiPredicate<Integer, Integer> p) {
List<Integer> newList = new ArrayList<>();
for(Integer i : l) {
if(p.test(i, i + 10)) {
newList.add(i);
}
}
return newList;
}
}

We can call the findNumbers() method:

List<Integer> list = Arrays.asList(12,5,45,18,33,24,40);

// Using an anonymous class


findNumbers(list, new BiPredicate<Integer, Integer>() {
public boolean test(Integer i1, Integer i2) {

24
return Numbers.isMoreThanFifty(i1, i2);
}
});

// Using a lambda expression


findNumbers(list, (i1, i2) -> Numbers.isMoreThanFifty(i1, i2));

// Using a method reference


findNumbers(list, Numbers::isMoreThanFifty);

5.4 Instance method reference of an object of a particular type

In this case, we have a lambda expression like the following:

(obj, args) -> obj.instanceMethod(args)

Where an instance of an object is passed, and one of its methods is executed with some optional(s)
parameter(s).

This can be turned into the following method reference:

ObjectType::instanceMethod

This time, the conversion is not that straightforward. First, in the method reference, we don't use the
instance itself—we use its type.

Second, the other argument of the lambda expression, if any, is not used in the method reference, but
it's passed behind the curtains like in the static method case.

For example, assuming this class:

class Shipment {
public double calculateWeight() {
double weight = 0;
// Calculate weight
return weight;
}
}

And this method:

public List<Double> calculateOnShipments(


List<Shipment> l, Function<Shipment, Double> f) {
List<Double> results = new ArrayList<>();
for(Shipment s : l) {
results.add(f.apply(s));
}
return results;

25
}

We can call that method using:

List<Shipment> l = new ArrayList<Shipment>();

// Using an anonymous class


calculateOnShipments(l, new Function<Shipment, Double>() {
public Double apply(Shipment s) { // The object
return s.calculateWeight(); // The method
}
});

// Using a lambda expression


calculateOnShipments(l, s -> s.calculateWeight());

// Using a method reference


calculateOnShipments(l, Shipment::calculateWeight);

In this example, we don't pass any arguments to the method. The key point here is that an instance of
the object is the parameter of the lambda expression, and we form the reference to the instance
method with the type of the instance.

Here's another example where we pass two arguments to the method reference.

Java has a Function interface that takes one parameter, a BiFunction that takes two parameters, but
there's no TriFunction that takes three parameters, so let's make one:

interface TriFunction<T, U, V, R> {


R apply(T t, U u, V v);
}

Now assume a class with a method that takes two parameters a return a result, like this:

class Sum {
Integer doSum(String s1, String s2) {
return Integer.parseInt(s1) + Integer.parseInt(s1);
}
}

We can wrap the doSum() method within a TriFunction implementation by using an anonymous class:

TriFunction<Sum, String, String, Integer> anon =


new TriFunction<Sum, String, String, Integer>() {
@Override
public Integer apply(Sum s, String arg1, String arg2) {
return s.doSum(arg1, arg1);
}
};
System.out.println(anon.apply(new Sum(), "1", "4"));

26
//Or by using a lambda expression:
TriFunction<Sum, String, String, Integer> lambda =
(Sum s, String arg1, String arg2) -> s.doSum(arg1, arg1);
System.out.println(lambda.apply(new Sum(), "1", "4"));

//Or just by using a method reference:


TriFunction<Sum, String, String, Integer> mRef = Sum::doSum;
System.out.println(mRef.apply(new Sum(), "1", "4"));

Here:

 The first type parameter of TriFunction is the object type that contains the method to
execute.
 The second type parameter of TriFunction is the type of the first parameter.
 The third type parameter of TriFunction is the type of the second parameter.
 The last type parameter of TriFunction is the return type of the method to execute. Notice
how this is omitted (inferred) in the lambda expression and the method reference.
 It may seem odd to just see the interface, the class, and how they are used with a method
reference; but this becomes more evident when you see the anonymous class or even the
lambda version.
From:

(Sum s, String arg1, String arg2) -> s.doSum(arg1, arg1)

To

Sum::doSum

5.5 Instance method reference of an existing object

In this case, we have a lambda expression like the following:

(args) -> obj.instanceMethod(args)

This can be turned into the following method reference:

obj::instanceMethod

This time, an instance defined somewhere else is used, and the arguments (if any) are passed behind
the curtains like in the static method case.

27
For example, assuming these classes:

class Car {
private int id;
private String color;
// More properties
// And getter and setters
}
class Mechanic {
public void fix(Car c) {
System.out.println("Fixing car " + c.getId());
}
}

And this method:

public void execute(Car car, Consumer<Car> c) {


c.accept(car);
}

We can call the method above using:

final Mechanic mechanic = new Mechanic();


Car car = new Car();

// Using an anonymous class


execute(car, new Consumer<Car>() {
public void accept(Car c) {
mechanic.fix(c);
}
});

// Using a lambda expression


execute(car, c -> mechanic.fix(c));

// Using a method reference


execute(car, mechanic::fix);

The key, in this case, is to use any object visible by an anonymous class/lambda expression and pass
some arguments to an instance method of that object.

Here's another quick example using another Consumer:

Consumer<String> c = System.out::println;
c.accept("Hello");

5.6 Constructor method reference

In this case, we have a lambda expression like the following:

28
(args) -> new ClassName(args)

That can be turned into the following method reference:

ClassName::new

The only thing this lambda expression does is to create a new object and we just reference a
constructor of the class with the keyword new. Like in the other cases, arguments (if any) are not
passed in the method reference.

Most of the time, we can use this syntax with two (or three) interfaces of the java.util.function package.

If the constructor takes no arguments, a Supplier will do the job:

// Using an anonymous class


Supplier<List<String>> s = new Supplier<List<String>>() {
public List<String> get() {
return new ArrayList<String>();
}
};
List<String> l = s.get();

// Using a lambda expression


Supplier<List<String>> s = () -> new ArrayList<>();
List<String> l = s.get();

// Using a method reference


Supplier<List<String>> s = ArrayList::new;
List<String> l = s.get();

If the constructor takes an argument, we can use the Function interface. For example:

// Using an anonymous class


Function<String, Integer> f =
new Function<String, Integer>() {
public Integer apply(String s) {
return new Integer(s);
}
};
Integer i = f.apply(100);

// Using a lambda expression


Function<String, Integer> f = s -> new Integer(s);
Integer i = f.apply(100);

// Using a method reference


Function<String, Integer> f = Integer::new;
Integer i = f.apply(100);
If the constructor takes two arguments, we use the BiFunction interface:
// Using a anonymous class
BiFunction<String, String, Locale> f = new BiFunction<String, String, Locale>() {
public Locale apply(String lang, String country) {

29
return new Locale(lang, country);
}
};
Locale loc = f.apply("en","UK");

// Using a lambda expression


BiFunction<String, String, Locale> f = (lang, country) -> new Locale(lang, country);
Locale loc = f.apply("en","UK");

// Using a method reference


BiFunction<String, String, Locale> f = Locale::new;
Locale loc = f.apply("en","UK");

If you have a constructor with three or more arguments, you would have to create your own functional
interface.

You can see that referencing a constructor is very similar to referencing a static method. The
difference is that the constructor "method name" is new.

5.7 Conclusion

Many of the examples presented here are very simple and they probably don't justify the use of
lambda expressions or method references.

As mentioned at the beginning, use method references if they make your code CLEARER.

For example, you can avoid the one method restriction by grouping all your code in a static method, and
create a reference to that method instead of using a class or a lambda expression with many lines.

But the real power of lambda expressions and method references comes when they are combined with
another new feature of Java 8; streams.

6 Spliterator?

Java Spliterator interface is an internal iterator that breaks the stream into the smaller parts. These smaller
parts can be processed in parallel.
In real life programming, we may never need to use Spliterator directly. Under normal operations, it will
behave exactly same as Java Iterator.

+ Spliterator Syntax
- Spliterator<T> spliterator = list.spliterator();

30
The Java collection classes provide default stream() and parallelStream() methods which internally use
the Spliterator through the call to the spliterator(). It helps in processing the collection data in parallel.

Collection.java

default Stream<E> stream() {


return StreamSupport.stream(spliterator(), false);
}

default Stream<E> parallelStream() {


return StreamSupport.stream(spliterator(), true);
}
6.1 Java Spliterator Features

Following is a list of features provided by Spliterator in Java.

1. Spliterator has been introduced in Java 8.


2. It provides support for parallel processing of stream of elements for any collection.
3. It provides tryAdvance() method to iterate elements individually in different threads. It helps in
parallel processing.
4. To iterate elements sequentially in a single Thread, use forEachRemaining() method.
5. The trySplit() method is used partition the spliterator, if it is possible.
6. It helps in combining the hasNext() and next() operations into one method.

6.2 Java Spliterator Methods

1. int characteristics() : returns the list of characteristics of the spliterator. It can be any of ORDERED,
DISTINCT, SORTED, SIZED, NONNULL, IMMUTABLE, CONCURRENT, and SUBSIZED.
2. long estimateSize() : returns an estimate of the number of elements that would be encountered by a
forEachRemaining() traversal, or returns Long.MAX_VALUE if infinite, unknown, or too expensive to
compute.
3. default void forEachRemaining(Consumer action) : performs the given action for each remaining
element, sequentially in the current thread, until all elements have been processed or the action
throws an exception.
4. default Comparator getComparator() : if the spliterator’s source is SORTED by a Comparator,
returns that Comparator.
5. default long getExactSizeIfKnown() : returns estimateSize() if this Spliterator is SIZED, else -1.
6. default boolean hasCharacteristics(int characteristics) : returns true if the dpliterator’s
characteristics() contain all of the given characteristics.
7. boolean tryAdvance(Consumer action) : if a remaining element exists, performs the given action
on it, returning true; else returns false.
8. Spliterator trySplit() : if the spliterator can be partitioned, returns a Spliterator covering elements,
that will, upon return from this method, not be covered by this Spliterator.

31
6.3 Java Spliterator Example

6.3.1 Spliterator characteristics() example

Java example to verify the characteristics of Spliterator obtained for ArrayList.

Spliterator example
ArrayList<String> list = new ArrayList<>();

Spliterator<String> spliterator = list.spliterator();

int expected = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;

System.out.println(spliterator.characteristics() == expected); //true

if (spliterator.hasCharacteristics(Spliterator.ORDERED)) {
System.out.println("ORDERED");
}

if (spliterator.hasCharacteristics(Spliterator.DISTINCT)) {
System.out.println("DISTINCT");
}

if (spliterator.hasCharacteristics(Spliterator.SORTED)) {
System.out.println("SORTED");
}

if (spliterator.hasCharacteristics(Spliterator.SIZED)) {
System.out.println("SIZED");
}

if (spliterator.hasCharacteristics(Spliterator.CONCURRENT)) {
System.out.println("CONCURRENT");
}

if (spliterator.hasCharacteristics(Spliterator.IMMUTABLE)) {
System.out.println("IMMUTABLE");
}

if (spliterator.hasCharacteristics(Spliterator.NONNULL)) {
System.out.println("NONNULL");
}

if (spliterator.hasCharacteristics(Spliterator.SUBSIZED)) {
System.out.println("SUBSIZED");
}

Program Output.

Console

32
true

ORDERED
SIZED
SUBSIZED

6.3.2 Spliterator estimateSize() and getExactSizeIfKnown() example

Java example to get the size of backing collection i.e. number of elements to iterate by spliterator.

Spliterator example
ArrayList<String> list = new ArrayList<>();

list.add("A");
list.add("B");
list.add("C");
list.add("D");

Spliterator<String> spliterator = list.spliterator();

System.out.println(spliterator.estimateSize());
System.out.println(spliterator.getExactSizeIfKnown());

Program Output.

Console
4
4

6.3.3 Spliterator getComparator() example

Java example to find the comparator used by spliterator.

Spliterator example
SortedSet<String> set = new TreeSet<>( Collections.reverseOrder() );

set.add("A");
set.add("D");
set.add("C");
set.add("B");

System.out.println(set);

System.out.println(set.spliterator().getComparator());

33
Program Output.

Console
[D, C, B, A]
java.util.Collections$ReverseComparator@7852e922

6.3.4 Spliterator trySplit() example

Java example to split the elements to two groups and iterate independently.

Spliterator example
ArrayList<String> list = new ArrayList<>();

list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
list.add("F");

Spliterator<String> spliterator1 = list.spliterator();


Spliterator<String> spliterator2 = spliterator1.trySplit();

spliterator1.forEachRemaining(System.out::println);

System.out.println("========");

spliterator2.forEachRemaining(System.out::println);

Program Output.

Console
D
E
F
========
A
B
C

34
6.3.5 Java example to perform hasNext() and next() operations in single statement using

forEachRemaining() method.

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class IteratorExample {


public static void main(String[] args) {
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Date");

// Get an iterator for the list


Iterator<String> iterator = fruits.iterator();

// Traditional approach using hasNext() and next()


System.out.println("Traditional approach:");
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.println(fruit);
}

// Reset the iterator


iterator = fruits.iterator();

// Using forEachRemaining() to perform hasNext() and next() in a single statement


System.out.println("\nUsing forEachRemaining():");
iterator.forEachRemaining(fruit -> System.out.println(fruit));

// Another example with additional processing


iterator = fruits.iterator();
System.out.println("\nUsing forEachRemaining() with more complex operation:");
iterator.forEachRemaining(fruit -> {
String processed = "Fruit: " + fruit.toUpperCase();
System.out.println(processed);
});
}
}

Key Points:
 forEachRemaining() internally uses hasNext() and next() to process all remaining elements
 It provides a more concise way to iterate when you want to perform the same operation on all
elements
 The method takes a Consumer functional interface as parameter
 After calling forEachRemaining(), the iterator will be exhausted (no more elements)
Output:

Traditional approach:
Apple
Banana

35
Cherry
Date

Using forEachRemaining():
Apple
Banana
Cherry
Date

Using forEachRemaining() with more complex operation:


Fruit: APPLE
Fruit: BANANA
Fruit: CHERRY
Fruit: DATE

This approach is particularly useful when you want to perform the same operation on all remaining elements
of the iterator.

6.3.6 What is the difference between Iterator and Spliterator?

1. Use in API: Iterator is used for Collection API while Spliterator is used for Stream API
2. Parallel programming: Iterator can be used for iterating the elements in Collection in sequential order
while Spliterator can be used for iterating the Stream elements in parallel or sequential order.

7 Internal Vs. External iterator?

7.1 External iteration

Till Java 7, the collections framework relied on the concept of external iteration, where a Collection
provides, by implementing Iterable, a means to enumerate its elements i.e. Iterator, and clients use this to
step sequentially through the elements of a collection. For example, if we wanted to get all strings in
uppercase, we would write:

public class IterationExamples {


public static void main(String[] args){
List<String> alphabets = Arrays.asList(new String[]{"a","b","b","d"});

for(String letter: alphabets){


System.out.println(letter.toUpperCase());
}
}
}

36
OR we can write like this:

public class IterationExamples {


public static void main(String[] args){
List<String> alphabets = Arrays.asList(new String[]{"a","b","b","d"});

Iterator<String> iterator = alphabets.listIterator();


while(iterator.hasNext()){
System.out.println(iterator.next().toUpperCase());
}
}
}

Above both code snippets are for external iteration. External iteration is straightforward enough, but it has
several problems:

1) Java’s for-each loop/iterator is inherently sequential, and must process the elements in the order
specified by the collection.
2) It limits the opportunity to manage the control flow, which might be able to provide better performance by
exploiting reordering of the data, parallelism, short-circuiting, or laziness.

7.2 Internal iteration

Sometimes the strong guarantees of the for-each loop (sequential, in-order) are desirable, but often are just
an disadvantage to performance. The alternative to external iteration is internal iteration, where instead of
controlling the iteration, client let it handle by library and only provide the code which must be executed for
all/some of data elements.

The internal-iteration equivalent of the previous example is:

public class IterationExamples {


public static void main(String[] args){
List<String> alphabets = Arrays.asList(new String[]{"a","b","b","d"});

alphabets.forEach(l -> l.toUpperCase());


}
}
Where external iteration mixes the “what” (uppercase) and the “how” (for loop/iterator), internal iteration lets
the client to provide only the “what” but lets the library control the “how”. This offers several potential
benefits: client code can be clearer because it need only focus on stating the problem, not the details of how
to go about solving it, and we can move complex optimization code into libraries where it can benefit all
users.

37
8 Stream API

A Stream in Java can be defined as a sequence of elements from a source that supports aggregate
operations on them. The source here refers to a Collections or Arrays who provides data to a Stream.

Stream keeps the ordering of the data as it is in the source. The aggregate operations or bulk
operations are operations which allow us to express common manipulations on stream elements easily
and clearly.

Before going ahead, it is important to learn that Java 8 Streams are designed in such a way that most of
the stream operations returns streams only. This help us creating chain of the stream operations. This is
called as pipe-lining. I will use this term multiple times in this post, so keep it in mind.

8.1 Java Stream vs. Collection

All of us have watch online videos on youtube or some other such website. When you start watching video,
a small portion of file is first loaded into your computer and start playing. You don’t need to download
complete video before start playing it. This is called streaming. I will try to relate this concept with respect to
collections and differentiate with Streams.

At the basic level, the difference between Collections and Streams has to do with when things are
computed. A Collection is an in-memory data structure, which holds all the values that the data structure
currently has—every element in the Collection has to be computed before it can be added to the Collection.
A Stream is a conceptually fixed data structure, in which elements are computed on demand. This
gives rise to significant programming benefits. The idea is that a user will extract only the values they
require from a Stream, and these elements are only produced—invisibly to the user—as and when required.
This is a form of a producer-consumer relationship.

In java, java.util.Stream represents a stream on which one or more operations can be performed.
Stream operations are either intermediate or terminal. While terminal operations return a result of a
certain type, intermediate operations return the stream itself so you can chain multiple method calls in
a row. Streams are created on a source, e.g. a java.util.Collection like lists or sets (maps are not
supported). Stream operations can either be executed sequential or parallel.

Based on above points, if we list down the various characteristics of Stream, they will be as follows:

 Not a data structure


 Designed for lambdas
 Do not support indexed access
 Can easily be outputted as arrays or lists
 Lazy access supported
 Parallelizable

38
8.2 Different ways to create streams

Below is the most popular different ways to build streams from collections.

8.2.1 Stream.of(val1, val2, val3….)

public class StreamBuilders


{
public static void main(String[] args)
{
Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7,8,9);
stream.forEach(p -> System.out.println(p));
}
}

8.2.2 Stream.of(arrayOfElements)

public class StreamBuilders


{
public static void main(String[] args)
{
Stream<Integer> stream = Stream.of( new Integer[]{1,2,3,4,5,6,7,8,9} );
stream.forEach(p -> System.out.println(p));
}
}

8.2.3 List.stream()

public class StreamBuilders


{
public static void main(String[] args)
{
List<Integer> list = new ArrayList<Integer>();

for(int i = 1; i< 10; i++){


list.add(i);
}

Stream<Integer> stream = list.stream();


stream.forEach(p -> System.out.println(p));
}
}

8.2.4 Stream.generate() or Stream.iterate()

public class StreamBuilders


{
public static void main(String[] args)
{

// generate
Stream<Date> stream = Stream.generate(Date::new);
stream.forEach(System.out::println);

39
// iterate
List<Integer> evens = Stream.iterate(0, n -> n + 2)
.limit(10)
.collect(Collectors.toList());
System.out.println(evens); // [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
}
}

8.2.5 String chars or String tokens

public class StreamBuilders


{
public static void main(String[] args)
{
IntStream stream = "12345_abcdefg".chars();
stream.forEach(p -> System.out.println(p));

//OR

Stream<String> stream = Stream.of("A$B$C".split("\\$"));


stream.forEach(p -> System.out.println(p));
}
}

There are some more ways apart from above list such as using Stream.Buider or using intermediate
operations. We will learn about them in separate posts time to time.

8.3 Convert streams to collections

I should rather say converting stream to other data structures.

Please note that it is not a true conversion. It’s just collecting the elements from the stream into a collection
or array.
8.3.1 Convert Stream to List – Stream.collect( Collectors.toList() )

public class StreamBuilders {


public static void main(String[] args){
List<Integer> list = new ArrayList<Integer>();
for(int i = 1; i< 10; i++){
list.add(i);
}
Stream<Integer> stream = list.stream();
List<Integer> evenNumbersList = stream.filter(i -> i%2 == 0).collect(Collectors.toList());
System.out.print(evenNumbersList);
}
}

8.3.2 Convert Stream to array – Stream.toArray( EntryType[]::new )

public class StreamBuilders {


public static void main(String[] args){

40
List<Integer> list = new ArrayList<Integer>();
for(int i = 1; i< 10; i++){
list.add(i);
}
Stream<Integer> stream = list.stream();
Integer[] evenNumbersArr = stream.filter(i -> i%2 == 0).toArray(Integer[]::new);
System.out.print(evenNumbersArr);
}
}
There are plenty of other ways also to collect stream into set, map or into multiple ways. Just go
through Collectors class and try to keep them in mind.

8.4 Core stream operations

Stream abstraction have a long list of useful functions for you. I am not going to cover them all, but I plan
here to list down all most important ones, which you must know first hand.
Before moving ahead, lets build a collection of String beforehand. We will build out example on this list, so
that it is easy to relate and understand.

List<String> memberNames = new ArrayList<>();


memberNames.add("Amitabh");
memberNames.add("Shekhar");
memberNames.add("Aman");
memberNames.add("Rahul");
memberNames.add("Shahrukh");
memberNames.add("Salman");
memberNames.add("Yana");
memberNames.add("Lokesh");
These core methods have been divided into 2 parts given below:

8.5 Intermediate operations

Intermediate operations return the stream itself so you can chain multiple method calls in a row. Let’s learn
important ones.
8.5.1 Stream.filter()

Filter accepts a predicate to filter all elements of the stream. This operation is intermediate which enables
us to call another stream operation (e.g. forEach) on the result.

memberNames.stream().filter((s) -> s.startsWith("A"))


.forEach(System.out::println);

Output:

Amitabh
Aman

8.5.2 Stream.map()

The intermediate operation map converts each element into another object via
the given function. The following example converts each string into an upper-

41
cased string. But you can also use map to transform each object into another
type.

memberNames.stream().filter((s) -> s.startsWith("A"))


.map(String::toUpperCase)
.forEach(System.out::println);

Output:

AMITABH
AMAN

8.5.3 Stream.sorted()

Sorted is an intermediate operation which returns a sorted view of the stream. The elements are sorted in
natural order unless you pass a custom Comparator.

memberNames.stream().sorted()
.map(String::toUpperCase)
.forEach(System.out::println);
Output:

AMAN
AMITABH
LOKESH
RAHUL
SALMAN
SHAHRUKH
SHEKHAR
YANA
Keep in mind that sorted does only create a sorted view of the stream without manipulating the ordering of
the backed collection. The ordering of memberNames is untouched.
8.5.4 Terminal operations

Terminal operations return a result of a certain type instead of again a Stream.


8.5.5 Stream.forEach()

This method helps in iterating over all elements of a stream and perform some operation on each of them.
The operation is passed as lambda expression parameter.

memberNames.forEach(System.out::println);

8.5.6 Stream.collect()

8.5.6.1 collect(Collector<? super T,A,R> collector)


collect() method used to receive elements from a steam and store them in a collection and mentioned in
parameter function.

List<String> memNamesInUppercase = memberNames.stream().sorted()


.map(String::toUpperCase)
.collect(Collectors.toList());

42
System.out.print(memNamesInUppercase);

Outpout: [AMAN, AMITABH, LOKESH, RAHUL, SALMAN, SHAHRUKH, SHEKHAR, YANA]

8.5.6.2 Java Stream collect() Method: Supplier, Accumulator, Combiner Explained

collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)

(With Examples & Use Cases)


The collect() method in Java Streams is a mutable reduction operation that transforms stream elements
into a single result (e.g., a List, Map, or custom object). It has three key components:

8.5.6.2.1 1. Supplier (Supplier<R>)

Purpose: Creates a fresh, empty container to hold results.

8.5.6.2.2 Example:

Supplier<StringBuilder> supplier = () -> new StringBuilder();


// Or method reference:
Supplier<StringBuilder> supplier = StringBuilder::new;

Use Case:
 Initializes a mutable container (e.g., StringBuilder, ArrayList, HashMap).
 Called once per thread in parallel streams.

8.5.6.2.3 Accumulator (BiConsumer<R, ? super T>)

Purpose: Defines how to add a stream element to the container.

8.5.6.2.4 Example:

BiConsumer<StringBuilder, String> accumulator = (sb, str) -> sb.append(str.toUpperCase());

Use Case:
 Processes each stream element and updates the container.

43
 Example: Adding strings to a List, summing numbers, etc.

8.5.6.2.5 Combiner (BiConsumer<R, R>)

Purpose: Merges partial results in parallel streams.

8.5.6.2.6 Example:

BiConsumer<StringBuilder, StringBuilder> combiner = (sb1, sb2) -> sb1.append(sb2);

Use Case:
 Combines results from multiple threads (e.g., merging two StringBuilder objects).
 Unused in sequential streams but required syntactically.

8.5.6.2.7 Full collect() Syntax

<R> R collect(
Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner
);

8.5.6.2.8 Practical Examples


8.5.6.2.9 Concatenate Strings with StringBuilder

List<String> words = List.of("Java", " ", "Streams");

String result = words.stream().collect(


StringBuilder::new, // Supplier
StringBuilder::append, // Accumulator
StringBuilder::append // Combiner
).toString();

System.out.println(result); // Output: "Java Streams"

8.5.6.2.10 2. Sum Numbers with a Custom Object

class SumHolder {
int sum = 0;
}

List<Integer> numbers = List.of(1, 2, 3, 4);

SumHolder sumHolder = numbers.stream().collect(


SumHolder::new, // Supplier
(holder, num) -> holder.sum += num, // Accumulator

44
(h1, h2) -> h1.sum += h2.sum // Combiner
);

System.out.println(sumHolder.sum); // Output: 10

8.5.6.2.11 3. Group Strings by Length (Using HashMap)

List<String> fruits = List.of("Apple", "Banana", "Cherry", "Date");

Map<Integer, List<String>> lengthMap = fruits.stream().collect(


HashMap::new, // Supplier
(map, fruit) -> map.computeIfAbsent(fruit.length(), k -> new ArrayList<>()).add(fruit), //
Accumulator
(map1, map2) -> map2.forEach((len, list) -> map1.merge(len, list, (l1, l2) -> {
l1.addAll(l2);
return l1;
})) // Combiner
);

System.out.println(lengthMap);
// Output: {5=[Apple], 6=[Banana, Cherry], 4=[Date]}

8.5.6.2.12 When to Use This Over Collectors.toList()?

Use Use
Scenario collect() Collectors.toList()
Need a custom container (e.g., StringBuilder, ✅ ❌
Stats)
Parallel stream processing ✅ ✅ (built-in support)
Simple collection (e.g., List, Set) ❌ ✅

8.5.6.2.13 Key Takeaways

1. Supplier → Creates the initial container (new StringBuilder(), new HashMap()).


2. Accumulator → Adds elements to the container (sb.append(str)).
3. Combiner → Merges results in parallel streams (map1.putAll(map2)).
4. Use collect() when:
o You need a mutable container.
o Working with parallel streams.
o Existing collectors (e.g., Collectors.toList()) don’t fit.
Would you like an example with thread-safe collections (e.g., ConcurrentHashMap)?

45
8.5.7 Stream.match()

Various matching operations can be used to check whether a certain predicate matches the stream. All of
those operations are terminal and return a boolean result.

boolean matchedResult = memberNames.stream()


.anyMatch((s) -> s.startsWith("A"));

System.out.println(matchedResult);

matchedResult = memberNames.stream()
.allMatch((s) -> s.startsWith("A"));

System.out.println(matchedResult);

matchedResult = memberNames.stream()
.noneMatch((s) -> s.startsWith("A"));

System.out.println(matchedResult);

Output:

true
false
false

8.5.8 Stream.count()

Count is a terminal operation returning the number of elements in the stream as a long.

long totalMatched = memberNames.stream()


.filter((s) -> s.startsWith("A"))
.count();

System.out.println(totalMatched);

Output: 2

8.5.9 Stream.reduce()

This terminal operation performs a reduction on the elements of the stream with the given function. The
result is an Optional holding the reduced value.

Optional<String> reduced = memberNames.stream()


.reduce((s1,s2) -> s1 + "#" + s2);

reduced.ifPresent(System.out::println);

46
Output: Amitabh#Shekhar#Aman#Rahul#Shahrukh#Salman#Yana#Lokesh

8.6 5. Stream short-circuit operations

Though, stream operations are performed on all elements inside a collection satisfying a predicate, It is
often desired to break the operation whenever a matching element is encountered during iteration. In
external iteration, you will do with if-else block. In internal iteration, there are certain methods you can use
for this purpose. Let’s see example of two such methods:

8.6.1 5.1. Stream.anyMatch()

This will return true once a condition passed as predicate satisfy. It will not process any more elements.

boolean matched = memberNames.stream()


.anyMatch((s) -> s.startsWith("A"));

System.out.println(matched);

Output: true

8.6.2 5.2. Stream.findFirst()

It will return first element from stream and then will not process any more element.

String firstMatchedName = memberNames.stream()


.filter((s) -> s.startsWith("L"))
.findFirst().get();

System.out.println(firstMatchedName);

Output: Lokesh

8.7 6. Parallelism in Java Steam

With the Fork/Join framework added in Java SE 7, we have efficient machinery for implementing parallel
operations in our applications. But implementing this framework is itself a complex task; and if not done

47
right; is a source of complex multi-threading bugs having potential to crash the application. With the
introduction of internal iteration, we got the possibility of operations to be done in parallel.

To enable parallelism, all you have to do is to create a parallel stream, instead of sequential stream. And to
surprise you, this is really very easy. In any of above listed stream examples, anytime you want to particular
job using multiple threads in parallel cores, all you have to call method parallelStream() method instead of
stream() method.

public class StreamBuilders {


public static void main(String[] args){
List<Integer> list = new ArrayList<Integer>();
for(int i = 1; i< 10; i++){
list.add(i);
}
//Here creating a parallel stream
Stream<Integer> stream = list.parallelStream();
Integer[] evenNumbersArr = stream.filter(i -> i
%2 == 0).toArray(Integer[]::new);
System.out.print(evenNumbersArr);
}
}

A key driver for this work is making parallelism more accessible to developers. While the Java platform
provides strong support for concurrency and parallelism already, developers face unnecessary impediments
in migrating their code from sequential to parallel as needed. Therefore, it is important to encourage idioms
that are both sequential- and parallel-friendly. This is facilitated by shifting the focus towards describing
what computation should be performed, rather than how it should be performed.

It is also important to strike the balance between making parallelism easier but not going so far as to make it
invisible. Making parallelism transparent would introduce non-determinism and the possibility of data races
where users might not expect it.

This is all what I wanted to share regarding basics of Stream abstraction introduced in java 8. I will be
talking about various other things related to Streams in future posts.

Happy Learning !!

Read More:

48
8.8 Stream Operations

8.8.1 Intermediate Operations

 filter()
 map()
 flatMap()
 distinct()
 sorted()
 peek()
 limit()
 skip()

8.8.2 Terminal Operations

 forEach()
 forEachOrdered()
 toArray()
 reduce()
 collect()
 min()
 max()
 count()
 anyMatch()
 allMatch()
 noneMatch()
 findFirst()
 findAny()

49
9 Default and Static Methods in Interfaces
9.1 Why Introduced?

 To enable interface evolution without breaking existing implementations


 Allow interfaces to provide default implementations
 Enable utility methods in interfaces via static methods
9.2 Default Methods

interface Vehicle {
// Regular abstract method
void start();
// Default method
default void honk() {
System.out.println("Default honk sound");
}
}

class Car implements Vehicle {


@Override
public void start() {
System.out.println("Car started");
}
// Inherits honk() default implementation
}

// Usage:
Vehicle car = new Car();
car.honk(); // "Default honk sound"

9.3 Static Methods

interface MathOperations {
static int add(int a, int b) {
return a + b;
}
}

// Usage:
int sum = MathOperations.add(5, 3); // 8

9.4 Multiple Inheritance Resolution

When two interfaces have default methods with same signature:

50
5. Class implementation wins
6. Sub-interface can override
7. Explicit override required if ambiguous:
interface A { default void foo() {} }
interface B { default void foo() {} }

class C implements A, B {
@Override public void foo() {
A.super.foo(); // Explicitly choose A's implementation
}
}

10 Optional Class
10.1 What is Optional class and what is it’s use?

Java SE 8 introduces new class in util package i.e java.util.Optional . In a nutshell, you can view Optional as
a single value container which either contains the value or not (then it is called as empty).
It is used to avoid NullPointerException. This Optional class concept is inspired from Haskell and Scala.

Example of Optional Class

a. Here is an Empty Optional

Optional<Soundcard> sc = Optional.empty();

b. Here is an Optional with non-null value

Soundcard soundcard = new Soundcard();

Optional<Soundcard> sc = Optional.of(soundcard);

The Optional class have various utilities method such as isPresent() which help coders to avoid making use
of null value checks.

51
10.2 Purpose

 Container object that may or may not contain a non-null value


 Forces explicit null checks
10.3 Example

import java.util.Optional;
import java.util.function.Supplier;

public class OptionalExamples {

public static void main(String[] args) {


// =================================================================
// 1. CREATION EXAMPLES
// =================================================================

// a) Empty Optional - represents no value


Optional<String> emptyOptional = Optional.empty();
System.out.println("Empty optional: " + emptyOptional);
// Output: Empty optional: Optional.empty

// b) Optional with non-null value - throws NPE if null is passed


Optional<String> nonNullOptional = Optional.of("Hello");
System.out.println("Non-null optional: " + nonNullOptional);
// Output: Non-null optional: Optional[Hello]

// c) Optional that may contain null - safe creation method


String possiblyNull = Math.random() > 0.5 ? "Present" : null;
Optional<String> nullableOptional = Optional.ofNullable(possiblyNull);
System.out.println("Nullable optional: " + nullableOptional);
// Output (varies): Nullable optional: Optional[Present]
// OR Nullable optional: Optional.empty

// =================================================================
// 2. VALUE ACCESS EXAMPLES
// =================================================================

// a) orElse() - provides default value if empty


String value1 = emptyOptional.orElse("Default Value");
System.out.println("\norElse() example: " + value1);
// Output: orElse() example: Default Value

// b) orElseGet() - lazy evaluation of default value


String value2 = emptyOptional.orElseGet(() -> {
System.out.println("Computing default value...");
return "Computed Default";
});
System.out.println("orElseGet() example: " + value2);
// Output:
// Computing default value...
// orElseGet() example: Computed Default

// c) orElseThrow() - throw exception if empty


try {
String value3 = emptyOptional.orElseThrow(() ->
new RuntimeException("Value not present"));
System.out.println("This won't be printed");

52
} catch (RuntimeException e) {
System.out.println("orElseThrow() example: " + e.getMessage());
}
// Output: orElseThrow() example: Value not present

// =================================================================
// 3. CONDITIONAL ACTIONS
// =================================================================

// a) ifPresent() - perform action only if value exists


System.out.println("\nifPresent() examples:");
nonNullOptional.ifPresent(val -> System.out.println("Value exists: " + val));
// Output: Value exists: Hello
emptyOptional.ifPresent(val -> System.out.println("This won't print"));
// Output: (none)

// b) ifPresentOrElse() (Java 9+) - perform action or alternative


System.out.println("\nifPresentOrElse() examples:");
nonNullOptional.ifPresentOrElse(
val -> System.out.println("Value exists: " + val),
() -> System.out.println("No value present")
);
// Output: Value exists: Hello
emptyOptional.ifPresentOrElse(
val -> System.out.println("This won't print"),
() -> System.out.println("Handling empty case")
);
// Output: Handling empty case

// =================================================================
// 4. TRANSFORMATION AND CHAINING
// =================================================================

// a) map() - transform value if present


Optional<String> transformed = nonNullOptional.map(String::toUpperCase);
System.out.println("\nmap() example: " + transformed);
// Output: map() example: Optional[HELLO]

// b) flatMap() - for nested Optionals


Optional<Optional<String>> nestedOptional = Optional.of(Optional.of("Nested"));
Optional<String> flattened = nestedOptional.flatMap(x -> x);
System.out.println("flatMap() example: " + flattened);
// Output: flatMap() example: Optional[Nested]

// c) filter() - conditionally keep value


Optional<String> filtered = nonNullOptional.filter(s -> s.length() > 10);
System.out.println("filter() example (no match): " + filtered);
// Output: filter() example (no match): Optional.empty
Optional<String> filtered2 = nonNullOptional.filter(s -> s.length() > 3);
System.out.println("filter() example (match): " + filtered2);
// Output: filter() example (match): Optional[Hello]

// =================================================================
// 5. PRACTICAL USE CASES
// =================================================================

System.out.println("\nPractical examples:");

// a) Safe method chaining


String result = Optional.ofNullable(getExternalValue())
.map(String::trim)

53
.filter(s -> !s.isEmpty())
.map(String::toUpperCase)
.orElse("DEFAULT");
System.out.println("Method chaining result: " + result);
// Possible Outputs:
// If getExternalValue() returns non-null: Method chaining result: VALID VALUE
// If getExternalValue() returns null: Method chaining result: DEFAULT

// b) Null-safe property access


Person person = new Person("John", null);
String streetName = Optional.ofNullable(person)
.flatMap(Person::getAddress)
.map(Address::getStreet)
.orElse("Unknown street");
System.out.println("Null-safe property access: " + streetName);
// Output: Null-safe property access: Unknown street

// c) Alternative to if-else null checks


Optional.ofNullable(getConfigValue())
.ifPresentOrElse(
config -> applyConfiguration(config),
() -> useDefaultConfiguration()
);
// Possible Outputs:
// If getConfigValue() returns non-null: Applying configuration: custom-config
// If getConfigValue() returns null: Using default configuration
}

// [Rest of the helper methods and classes remain the same...]


}

10.4 Key Methods

1. Optional Creation:
o Optional.empty() - Explicit empty container

o Optional.of() - For known non-null values (throws NPE if null)

o Optional.ofNullable() - Safe creation when value might be null

2. Value Access:
o orElse() - Simple default value

o orElseGet() - Lazy evaluation with Supplier

o orElseThrow() - Throw custom exception if empty

3. Conditional Operations:
o ifPresent() - Consumer-based action

o ifPresentOrElse() - Both value and empty case handlers

4. Transformation:
o map() - Value transformation

54
o flatMap() - Flatten nested Optionals

o filter() - Conditional filtering

5. Practical Patterns:
o Safe null handling in method chains

o Deep property access without NPEs

o Clean alternative to if-else null checks

10.5 Best Practices

 Use as return type for methods that might not return value
 Never use for class fields or method parameters
 Avoid Optional.get() without checking isPresent()

11 java.time API
11.1 Core Classes

// Date without time


LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(1990, Month.JANUARY, 1);

// Time without date


LocalTime now = LocalTime.now();

// Date and time


LocalDateTime current = LocalDateTime.now();

// With timezone
ZonedDateTime zoned = ZonedDateTime.now(ZoneId.of("Europe/Paris"));

// Duration (time-based)
Duration duration = Duration.between(startTime, endTime);

// Period (date-based)
Period period = Period.between(startDate, endDate);

11.1.1 Formatting

DateTimeFormatter formatter = DateTimeFormatter .ofPattern("yyyy-MM-dd HH:mm:ss");


String formatted = current.format(formatter);
LocalDateTime parsed = LocalDateTime.parse("2023-01-01 12:00", formatter);

55
12 CompletableFuture

13 Enhanced Collection Methods


13.1 New Methods

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

// Remove conditionally
list.removeIf(s -> s.equals("b")); // ["a", "c"]

// Transform all elements


list.replaceAll(String::toUpperCase); // ["A", "C"]

// Map enhancements
Map<String, Integer> map = new HashMap<>();
map.putIfAbsent("a", 1);
map.compute("a", (k, v) -> v + 1); // a=2
map.merge("a", 1, Integer::sum); // a=3

13.2 ForEach with Maps

map.forEach((k, v) -> System.out.println(k + ": " + v));

14 Type and Repeating Annotations


14.1 Type Annotations

void process(@NonNull String input) {


// Compiler warning if input might be null
}

56
14.2 Repeating Annotations

@Repeatable(Schedules.class)
@interface Schedule {
String time();
}

@interface Schedules {
Schedule[] value();
}

@Schedule(time = "10:00")
@Schedule(time = "15:00")
class Meeting {}

15 Other Notable Features


15.1 String Joiner

StringJoiner joiner = new StringJoiner(", ", "[", "]");


joiner.add("A").add("B").add("C"); // [A, B, C]

// Shorthand
String joined = String.join("-", "A", "B", "C"); // A-B-C

15.2 Parallel Array Sorting

int[] numbers = {5, 3, 9, 1};


Arrays.parallelSort(numbers); // [1, 3, 5, 9]

15.3 Nashorn JavaScript Engine

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");


engine.eval("print('Hello Nashorn!')");

57

You might also like