Java Lambda expression details and examples

  • 2020-04-01 03:23:18
  • OfStack

Introduction to the

Lambda expressions are an important new feature in Java SE 8. Lambda expressions allow you to replace functional interfaces with expressions. Lambda expressions, like methods, provide a normal list of arguments and a body (body, which can be an expression or a block of code) that USES them.

Lambda expressions also enhance the collection library. Java SE 8 adds two packages for batch manipulation of collection data: the java.util.function package and the java.util.stream package. A stream is like an iterator, but with a lot of extra functionality. Overall,lambda expressions and streams are the biggest changes since the Java language added Generics and annotations. In this article, we'll look at the power of lambda expressions and streams from simple to complex examples.

Environment to prepare

If you don't already have Java 8 installed, you should install it before you can use lambda and stream. Tools and ides such as NetBeans and IntelliJ IDEA support Java 8 features, including lambda expressions, repeatable annotations, compact profiles, and other features.

Syntax for Lambda expressions

Basic grammar:
(parameters) - > expression
or
(parameters) - > {statements; }

Here is a simple example of a Java lambda expression:


//1. No parameters are required, the return value is 5
() -> 5
//2. Take a parameter (numeric type) and return 2 times its value
x -> 2 * x
//3. Take two parameters (Numbers) and return their difference
(x, y) -> x  �  y
//Receive two int integers and return their sum
(int x, int y) -> x + y
//5. Take a string object and print it in the console, returning no value (looks like it returns void)
(String s) -> System.out.print(s)

A basic Lambda example

Now that we know what a lambda expression is, let's start with some basic examples. In this section, we'll see how lambda expressions affect the way we code. Suppose there is a List of players that the programmer can traverse using the for statement ("for loop "), which in Java SE 8 can be converted to another form:


String[] atp = {"Rafael Nadal", "Novak Djokovic",
       "Stanislas Wawrinka",
       "David Ferrer","Roger Federer",
       "Andy Murray","Tomas Berdych",
       "Juan Martin Del Potro"};
List<String> players =  Arrays.asList(atp);
//The old loop
for (String player : players) {
     System.out.print(player + "; ");
}
//Using lambda expressions and functional operation
players.forEach((player) -> System.out.print(player + "; "));

//Use the double colon operator in Java 8
players.forEach(System.out::println);

As you can see,lambda expressions can reduce our code to one line. Another example is in graphical user interface programs, where anonymous classes can use lambda expressions instead. Similarly, when implementing the Runnable interface, you can use this:


//Use anonymous inner classes
btn.setOnAction(new EventHandler<ActionEvent>() {
          @Override
          public void handle(ActionEvent event) {
              System.out.println("Hello World!"); 
          }
    });

//Or use lambda expression
btn.setOnAction(event -> System.out.println("Hello World!"));

Here is an example of using lambdas to implement the Runnable interface:


//1.1 use anonymous inner classes
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello world !");
    }
}).start();
//1.2 use lambda expression
new Thread(() -> System.out.println("Hello world !")).start();
//2.1 use anonymous inner classes
Runnable race1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello world !");
    }
};
//2.2 use of lambda expression
Runnable race2 = () -> System.out.println("Hello world !");

//Call the run method directly (no new thread opened!)
race1.run();
race2.run();


Lambda expressions for Runnable, using block format, convert five lines of code into a single line statement. Next, in the next section we will use lambdas to sort collections.

Sort collections using Lambdas

In Java, the Comparator class is used to sort collections. In the following example, we will base the name, surname, name length and last letter of the player. As in the previous example, we used anonymous inner classes to sort, and then we used lambda expressions to simplify our code.
In the first example, we will sort the list by name. Using the old way, the code looks like this:


String[] players = {"Rafael Nadal", "Novak Djokovic", 
    "Stanislas Wawrinka", "David Ferrer",
    "Roger Federer", "Andy Murray",
    "Tomas Berdych", "Juan Martin Del Potro",
    "Richard Gasquet", "John Isner"};

//1.1 use anonymous inner classes to sort players by name
Arrays.sort(players, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return (s1.compareTo(s2));
    }
});

With lambdas, the same functionality can be achieved with the following code:


//1.2 use lambda expression to sort players
Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));
Arrays.sort(players, sortByName);
//1.3 it can also take the following form:
Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2)));

The other orders are shown below. As in the above example, the code implements comparators using anonymous inner classes and some lambda expressions, respectively:


//1.1 use anonymous inner classes to sort players by surname
Arrays.sort(players, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return (s1.substring(s1.indexOf(" ")).compareTo(s2.substring(s2.indexOf(" "))));
    }
});
//1.2 use lambda expression sort, according to surname
Comparator<String> sortBySurname = (String s1, String s2) -> 
    ( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) );
Arrays.sort(players, sortBySurname);
//1.3 or so, doubt if the original author is wrong, lots of parentheses...
Arrays.sort(players, (String s1, String s2) -> 
      ( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) ) 
    );
//2.1 use an anonymous inner class to sort players by name lenght
Arrays.sort(players, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return (s1.length() - s2.length());
    }
});
//2.2 use lambda expression sort according to name lenght
Comparator<String> sortByNameLenght = (String s1, String s2) -> (s1.length() - s2.length());
Arrays.sort(players, sortByNameLenght);
// 2.3 or this
Arrays.sort(players, (String s1, String s2) -> (s1.length() - s2.length()));
//3.1 use the anonymous inner class to sort players according to the last letter
Arrays.sort(players, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
    }
});
//3.2 sort by the last letter using lambda expression
Comparator<String> sortByLastLetter = 
    (String s1, String s2) -> 
        (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
Arrays.sort(players, sortByLastLetter);
// 3.3 or this
Arrays.sort(players, (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)));

That's it, simple and intuitive. In the next section, we'll explore more lambdas' capabilities and use them in conjunction with stream.

Use Lambdas and Streams

Stream is a wrapper around a collection and is commonly used with lambda. Many operations can be supported with lambdas, such as map, filter, limit, sorted, count, min, Max, sum, collect, and so on. Also, streams use lazy arithmetic, they don't actually read all the data, and a method like getFirst() ends the chain syntax. In the following examples, we'll explore what lambdas and streams can do. We created a Person class and used it to add some data to the list for further flow operations. Person is just a simple POJO class:


public class Person {
private String firstName, lastName, job, gender;
private int salary, age;
public Person(String firstName, String lastName, String job,
                String gender, int age, int salary)       {
          this.firstName = firstName;
          this.lastName = lastName;
          this.gender = gender;
          this.age = age;
          this.job = job;
          this.salary = salary;
}
// Getter and Setter 
// . . . . .
}

Next, we'll create two lists, both for the Person object:


List<Person> javaProgrammers = new ArrayList<Person>() {
  {
    add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000));
    add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500));
    add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800));
    add(new Person("Sindy", "Jonie", "Java programmer", "female", 32, 1600));
    add(new Person("Vere", "Hervey", "Java programmer", "male", 22, 1200));
    add(new Person("Maude", "Jaimie", "Java programmer", "female", 27, 1900));
    add(new Person("Shawn", "Randall", "Java programmer", "male", 30, 2300));
    add(new Person("Jayden", "Corrina", "Java programmer", "female", 35, 1700));
    add(new Person("Palmer", "Dene", "Java programmer", "male", 33, 2000));
    add(new Person("Addison", "Pam", "Java programmer", "female", 34, 1300));
  }
};
List<Person> phpProgrammers = new ArrayList<Person>() {
  {
    add(new Person("Jarrod", "Pace", "PHP programmer", "male", 34, 1550));
    add(new Person("Clarette", "Cicely", "PHP programmer", "female", 23, 1200));
    add(new Person("Victor", "Channing", "PHP programmer", "male", 32, 1600));
    add(new Person("Tori", "Sheryl", "PHP programmer", "female", 21, 1000));
    add(new Person("Osborne", "Shad", "PHP programmer", "male", 32, 1100));
    add(new Person("Rosalind", "Layla", "PHP programmer", "female", 25, 1300));
    add(new Person("Fraser", "Hewie", "PHP programmer", "male", 36, 1100));
    add(new Person("Quinn", "Tamara", "PHP programmer", "female", 21, 1000));
    add(new Person("Alvin", "Lance", "PHP programmer", "male", 38, 1600));
    add(new Person("Evonne", "Shari", "PHP programmer", "female", 40, 1800));
  }
};

Now we use the forEach method to iterate over the output of the above list:


System.out.println(" The names of all programmers :");
javaProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
phpProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

We also used the forEach method to increase the programmer's salary by 5%:


System.out.println(" Give programmers a raise  5% :");
Consumer<Person> giveRaise = e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary());
javaProgrammers.forEach(giveRaise);
phpProgrammers.forEach(giveRaise);

Another useful method is filter(), which lets us show PHP programmers who earn more than $1,400 a month:


System.out.println(" Below is the monthly salary over  $1,400  the PHP The programmer :")
phpProgrammers.stream()
          .filter((p) -> (p.getSalary() > 1400))
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

We can also define filters and reuse them to perform other operations:


//Define the filters
Predicate<Person> ageFilter = (p) -> (p.getAge() > 25);
Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400);
Predicate<Person> genderFilter = (p) -> ("female".equals(p.getGender()));
System.out.println(" Below is the age greater than  24 Years old and monthly salary $1,400 More than female PHP The programmer :");
phpProgrammers.stream()
          .filter(ageFilter)
          .filter(salaryFilter)
          .filter(genderFilter)
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
//Reuse of filters
System.out.println(" Older than  24 Women aged  Java programmers:");
javaProgrammers.stream()
          .filter(ageFilter)
          .filter(genderFilter)
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

Use the limit method to limit the number of result sets:


System.out.println(" The front of 3 a  Java programmers:");
javaProgrammers.stream()
          .limit(3)
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

System.out.println(" The front of 3 A woman  Java programmers:");
javaProgrammers.stream()
          .filter(genderFilter)
          .limit(3)
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

The sorting? Can we handle it in the stream? The answer is yes. In the following example, we will sort Java programmers by name and salary, put them into a list, and then display the list:


System.out.println(" According to the  name  The sorting , And show the former 5 a  Java programmers:");
List<Person> sortedJavaProgrammers = javaProgrammers
          .stream()
          .sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName())))
          .limit(5)
          .collect(toList());
sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));

System.out.println(" According to the  salary  The sorting  Java programmers:");
sortedJavaProgrammers = javaProgrammers
          .stream()
          .sorted( (p, p2) -> (p.getSalary() - p2.getSalary()) )
          .collect( toList() );
sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));

If we are only interested in the lowest and highest salaries, the min and Max methods are faster than selecting the first/last after sorting:


System.out.println(" minimum-paid  Java programmer:");
Person pers = javaProgrammers
          .stream()
          .min((p1, p2) -> (p1.getSalary() - p2.getSalary()))
          .get()
System.out.printf("Name: %s %s; Salary: $%,d.", pers.getFirstName(), pers.getLastName(), pers.getSalary())
System.out.println(" highest-paid  Java programmer:");
Person person = javaProgrammers
          .stream()
          .max((p, p2) -> (p.getSalary() - p2.getSalary()))
          .get()
System.out.printf("Name: %s %s; Salary: $%,d.", person.getFirstName(), person.getLastName(), person.getSalary())

In the above example we have seen how the collect method works. Combined with the map method, we can use the collect method to put our result Set into a string, a Set or a TreeSet:


System.out.println(" will  PHP programmers  the  first name  Concatenate into strings :");
String phpDevelopers = phpProgrammers
          .stream()
          .map(Person::getFirstName)
          .collect(joining(" ; ")); //It can be used as a token & NBSP; in further operations;  
System.out.println(" will  Java programmers  the  first name  Deposit to  Set:");
Set<String> javaDevFirstName = javaProgrammers
          .stream()
          .map(Person::getFirstName)
          .collect(toSet());
System.out.println(" will  Java programmers  the  first name  Deposit to  TreeSet:");
TreeSet<String> javaDevLastName = javaProgrammers
          .stream()
          .map(Person::getLastName)
          .collect(toCollection(TreeSet::new));

Streams can also be parallel. Here's an example:


System.out.println(" Calculation of pay  Java programmers  All of the money:");
int totalSalary = javaProgrammers
          .parallelStream()
          .mapToInt(p -> p.getSalary())
          .sum();

We can use the summaryStatistics method to get various summary data for the elements in the stream. Next, we can access these methods, such as getMax, getMin, getSum, or getAverage:


//Calculate count, min, Max, sum, and average for Numbers
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics stats = numbers
          .stream()
          .mapToInt((x) -> x)
          .summaryStatistics();
System.out.println("List The largest number in  : " + stats.getMax());
System.out.println("List The smallest number in  : " + stats.getMin());
System.out.println(" The sum of all the Numbers    : " + stats.getSum());
System.out.println(" The average of all the Numbers  : " + stats.getAverage()); 

OK, that's it, I hope you like it!

conclusion

In this article, we've learned different ways to use lambda expressions, from basic examples to complex examples using lambdas and streams. In addition, we learned how to use lambda expressions with Comparator classes to sort Java collections.


Although it may seem advanced, the essence of a Lambda expression is simply "syntactic sugar," which the compiler infers and converts into regular code, so you can do the same thing with less code. I recommend not to mess with it, because it is as simple, difficult to understand, and difficult to debug as the code written by some very advanced hackers.


Related articles: