Java Lambda Expression & Stream API

Rakib Hasan
7 min readJul 10, 2021

--

lambda-expression

Lambda Expression
The Expression through which can represent an Annonymous funtion

Anonymous: doesn’t have an explicit name like a method would normally have.

Syntax

(argument-list) -> {body}

Here,

  • Argument-list: It can be empty or non-empty as well.
  • Arrow-token: It is used to link arguments-list and body of expression.
  • Body: It contains expressions and statements for lambda expression.

In traditional approach can make an add method like bellow:

public int add (int a, int b) {
return a+b;
}

And If we wirte this code using lambda expression then,

(a,b) -> { return a+b; } 
or
(a,b) -> a+b
Others example like no parameter,
() -> {expression;}
If method argument is one,
a -> expression or a -> {expression;}

The Lambda expression only can be applicable for Functional Interface, the method which is present in the functional interface, for that particular method only we can write the lambda expression.

Functional Interface
The interface who contains only one abstract method but can have multiple default and static method, is called Functional Interface.
Example:

  • Runnable → run()
  • Callable → cal()
  • Comparable → compareTo()
  • Comparator →compare()

Suppose we are creating a Calculator interface which should be Functional interface like-

public interface Calculator {
public int add(int a, int b);
}
public class Test {
public static void main(String[] args) {
Calculator calculator = (a, b) -> a+b;
System.out.printf("sum of 2 & 3 is " + calculator.add(2,3));
}
}

We can discuss some Functional Interface which are needed in stream APIs.

Consumer- A Consumer is a functional interface that accepts a single input and returns no output.A Consumer is a functional interface that accepts a single input and returns no output.

void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after);
Example:
Consumer<String> consumer =(name) -> {
System.out.println("Name is: " + name);
};
consumer.accept("Rakib");

The accept method is the Single Abstract Method (SAM) which accepts a single argument of type T. Whereas, the other one andThen is a default method used for composition.

Predicate- Predicate interface represents a boolean-valued-function of an argument. This is mainly used to filter data from a Java Stream. The filter method of a stream accepts a predicate to filter the data and return a new stream satisfying the predicate. A predicate has a test() method which accepts an argument and returns a boolean value.

boolean test(T t)
Example:
Predicate<Integer> predicate = number -> number % 2 == 0;
Sout("Result of predicate test = " + predicate.test(4));

Supplier-Supplier is a simple interface which indicates that this implementation is a supplier of results.The supplier has only one method get() and does not have any other default and static methods.

T get()
Example:
Supplier<String> supplier = () -> "Hello world";
System.out.println(supplier.get());

Example Usecase of Consumer, Predicate, Supplier:

// List of some name
List<String> list = Arrays.asList("rakib","imran","rashed","atik");
// Predicate - start with h
Predicate<String> predicateStartWithH = (name) -> name.startsWith("h");
// Predicate - start with r
Predicate<String> predicateStartWithR = (name) -> name.startsWith("r");
// Consumer - Print the name variable
Consumer<String> consumer = (name) -> System.out.println(name);
// Supplier - return 'Not Found'
Supplier<String> supplier = () -> "Not Found";
// Print the name which are started with r
list.stream().filter(predicateStartWithR).forEach(consumer);
// Print the name started with h, if not found print supplier message
System.out.println(list.stream().filter(predicateStartWithH).findAny().orElseGet(supplier));

Here filter() method’s parameter type is predicate, foreach() method’s parameter is consumer, orElseGet() method’s parameter is supplier. For understanding stream API we need to understand first about these method internal working process.For understanding stream api in deeped, we need to study on Optional & method reference.

Optional<?>

To avoid unpredictable NullPointerException, Java 8 introduced one class called Optional (Ref). Optional class has three static methods like-

//return empty optional object
Optional<Object> emptyOptional = Optional.empty()
//creating optional object with provided parameter.
//If object is null, it will return the null pointer exception
//If we know the object we are passing, is not null,
//then we will go for Optional.of(),
//because it will directly check the optional
Optional<Object> objectOptional = Optional.of(Object)
//Optional.ofNullable() is not returned any null pointer exception, //because it will internal check - if object is null return empty //object, otherwise retutn the actual object
Optional<Object> objectOptional = Optional.ofNullable()

Example of Usecase:

//customer class
class Customer {
private int id;
private String name;
private String email;
//getter,setter & Constructor
}
//search customer by name
public Customer searchCustomerByName(List<Customer> customerList, String name) throws Exception {
return customerList.stream()
.filter(customer -> customer.getName().equals(name))
.findAny()
.orElseThrow(() -> new Exception("No customer found"));
}
//Some Usecase of OptionalCustomer customer = new Customer(123, "rashed", null);
Optional<String> emailOptional = Optional.ofNullable(customer.getEmail());
System.out.println(emailOptional.orElseGet(() -> "Default Email"));
Optional<String> emailOptional1 = Optional.ofNullable(new Customer(123, "rashed", "rashed@gmail.com").getEmail());
System.out.println(emailOptional1.map(String::toUpperCase).orElseGet(() -> "Default Email"));

List<Customer> customers = Arrays.asList(
new Customer(123, "hasan", "hasan@gmail.com"),
new Customer(123, "rahmot", "rahmot@gmail.com"),
new Customer(123, "Gaji", "gaji@gmail.com")
);
try {
Customer searchCustomer = searchCustomerByName(customers, "x");
} catch (Exception e) {
e.printStackTrace();
}

For reactive programming generally good practice is using, here in customer class we should design getter method with optional data which can be nullable for some cases.

In java 8, Good developers are using Method reference frequently in Lambda Expression. Let’s learn about Method Reference.

Method Reference- Method reference is used to refer method of functional interface. It is compact and easy form of lambda expression. Each time when you are using lambda expression to just referring a method, you can replace your lambda expression with method reference. The method references can only be used to replace a single method of the lambda expression.

Syntax:

(Class/Instance)::method name
Example: Person::getAge

Types of Method Reference

  1. Reference to a static method.
interface Sayable{  
void say();
}
public class MethodReference {
public static void saySomething(){
System.out.println("Hello");
}
public static void main(String[] args) {
// Referring static method
Sayable sayable = MethodReference::saySomething;
// Calling interface method
sayable.say(); //Hello
}
}
Parameter using,
import java.util.function.BiFunction;

class Arithmetic {
public static int add(int a, int b) {
return a + b;
}
}

public class MethodReference3 {
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> adder = Arithmetic::add;
int result = adder.apply(10, 20);
System.out.println(result); //30
}
}
  1. Reference to an instance method.
interface Sayable{  
void say();
}
public class MethodReference {
public void saySomething(){
System.out.println("Hello");
}
public static void main(String[] args) {
MethodReference instance = new MethodReference();
// Referring instance method
Sayable sayable = instance::saySomething;
// Calling interface method
sayable.say(); //Hello
}
}
  1. Reference to a constructor: You can refer a constructor by using the new keyword. Here, we are referring constructor with the help of functional interface.

Syntax & Example

ClassName::newExample:
interface MessageAble {
Message getMessage(String msg);
}
class Message{
Message(String msg){
System.out.print(msg); //Hello
}
}
public class ConstructorReference {
public static void main(String[] args) {
MessageAble hello = Message::new;
hello.getMessage("Hello");
}
}

Stream API

A Stream API is used to process Collections of Objects
A Stream is a sequence of Objects that supports verious methods which can be pipelined to produce the desired result.

A Stream is not a data structure instead it takes input from the collections, Arrays or I/O channels.

Stream don’t change the original data structure, they only provide the result as per the pipelined methods.

There are several methods provided by Stream APIs. we are describing one by one.

Filter- for Conditional check

Foreach- for iteration

Example of filter & foreach:

List<Integer> list = Arrays.asList(1,2,3,4,5);
Consumer<Integer> consumer = (s) -> {System.out.println(s);};
list.stream().forEach(consumer);
Another one: Map Iteration
Map<Integer, String> map = new HashMap<>();
map.put(1, "cat");
map.put(2, "dog");
map.put(3, "elephant");

BiConsumer<Integer, String> biConsumer = (key, value) -> System.out.println(key + " : " + value);
map.forEach(biConsumer);
map.entrySet()stream().forEach(System.out::println);
//Filter: print only even numbers
Predicate<Integer> predicate = (t) -> t % 2 == 0;
list.stream().filter(predicate).forEach(consumer);
// Print only even key's value
map.entrySet().stream().filter(k -> (k.getKey()%2 == 0)).forEach(System.out::println);
//Using collect method we can create a list/set from existing one //based on current filter
System.out.println(map.entrySet().stream().filter(k -> (k.getKey()%2 == 0)).collect(Collectors.toList()));

Sort a List

class Employee {
private int id;
private String name;
private String dept;
private long salary;
}
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(176, "rashed", "IT", 60000));
employees.add(new Employee(388, "habib", "CIVIL", 90000));
employees.add(new Employee(470, "milon", "DEFENCE", 50000));
employees.add(new Employee(624, "karim", "CORE", 40000));
employees.add(new Employee(176, "rubel", "SOCIAL", 120000));

//using Collections sort method
Collections.sort(employees, new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
return (int) (o1.getSalary() - o2.getSalary());// ascending
}
});

// same as above
Collections.sort(employees, ( o1, o2) ->(int) (o1.getSalary() - o2.getSalary()));
System.out.println(employees);

//same as the above two lines- descending order
employees.stream().sorted(( o1, o2) ->(int) (o2.getSalary() - o1.getSalary())).forEach(System.out::println);

//Using Comparator
employees.stream().sorted(Comparator.comparing(Employee::getSalary)).forEach(System.out::println);

Sort a Map

Map<Employee, Integer> employees = new TreeMap<>((o1, o2) -> (int) (o2.getSalary() - o1.getSalary()));
employees.put(new Employee(176, "rashed", "IT", 60000), 60);
employees.put(new Employee(388, "habib", "CIVIL", 90000), 90);
employees.put(new Employee(470, "milon", "DEFENCE", 50000), 50);
employees.put(new Employee(624, "karim", "CORE", 40000), 40);
employees.put(new Employee(176, "rubel", "SOCIAL", 120000), 120);

System.out.println(employees);
Comparator<Employee> comparator = Comparator.comparing(Employee::getSalary).reversed();
employees.entrySet().stream()
.sorted(Map.Entry.comparingByKey(comparator))
.forEach(System.out::println);

Map Reduce

Map-Reduce is a functional programming model, it serves our 2 purposes:

  1. Map → Tranforming data
  2. Reduce → Aggregating data (combine elements of stream & produce a single value)

Reduce Method

T reduce(T identity, BinaryOperator<T> accumulator);

Here identity is the initial value of Type T, accumulator is the function for combining two values

Example

Integer sumOfArrays = Stream.of(1,2,3,4,5,6).reduce(0,(a,b)->a+b);

Here identity: 0, is the initial value, accumulator: (a,b)->a+b function

Elaborate Example:

List<Integer> numbers = Arrays.asList(3, 7, 8, 1, 5, 9);List<String> words = Arrays.asList("corejava", "spring", "hibernate");int sum = 0;
for (int no : numbers) {
sum = sum + no;
}
System.out.println(sum);
int sum1 = numbers.stream().mapToInt(i -> i).sum();
System.out.println(sum1);
Integer reduceSum = numbers.stream().reduce(0, (a, b) -> a + b);
System.out.println(reduceSum);
Optional<Integer> reduceSumWithMethodReference = numbers.stream().reduce(Integer::sum);
System.out.println(reduceSumWithMethodReference.get());
Integer mulResult = numbers.stream().reduce(1, (a, b) -> a * b);
System.out.println(mulResult);
Integer maxvalue = numbers.stream().reduce(0, (a, b) -> a > b ? a : b);
System.out.println(maxvalue);
Integer maxvalueWithMethodReference = numbers.stream().reduce(Integer::max).get();
System.out.println(maxvalueWithMethodReference);
String longestString = words.stream()
.reduce((word1, word2) -> word1.length() > word2.length() ? word1 : word2).get();
System.out.println(longestString);

Example with Real life Object

class Employee {

private int id;
private String name;
private String dept;
private long salary;
private String grade;
}
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(176, "rashed", "IT", 60000, "A"));
employees.add(new Employee(388, "habib", "CIVIL", 90000, "B"));
employees.add(new Employee(470, "milon", "DEFENCE", 50000, "A"));
employees.add(new Employee(624, "karim", "CORE", 40000, "C"));
employees.add(new Employee(176, "rubel", "SOCIAL", 120000, "A"));

double avgSalary = employees.stream()
.filter(employee -> employee.getGrade().equalsIgnoreCase("A"))
.map(employee -> employee.getSalary())
.mapToDouble(i -> i)
.average().getAsDouble();

System.out.println(avgSalary);

double sumSalary = employees.stream()
.filter(employee -> employee.getGrade().equalsIgnoreCase("A"))
.map(employee -> employee.getSalary())
.mapToDouble(i -> i)
.sum();
System.out.println(sumSalary);

Reference:

  1. Java Techie

--

--