Introduction
Lambda expressions are an important new feature in Java SE 8. Lambda expressions allow you to replace functional interfaces with expressions. A lambda expression is just like a method, it provides a normal parameter list and a body (which can be an expression or a code block) that uses these parameters.
Lambda expressions also enhance the collections library. Java SE 8 adds two packages for batch operations on collection data: the java.util.function package and the java.util.stream package. A stream is just like an iterator, but with many additional features. Overall, lambda expressions and streams are the biggest changes since the addition of generics and annotations to the Java language. In this article, we will see the power of lambda expressions and streams from simple to complex examples.
Environmental preparation
If Java 8 has not been installed, you should install it first before you can use lambda and stream (the translator recommends installing it in a virtual machine for testing). Tools and IDEs like NetBeans and IntelliJ IDEA support Java 8 features, including lambda expressions, repeatable annotations, compact profiles and other features.
Lambda expression syntax
Basic syntax:
(parameters) -> expression
or
(parameters) ->{ statements; }
Here is a simple example of a Java lambda expression:
Copy the code code as follows:
// 1. No parameters are required, the return value is 5
() -> 5
// 2. Receive a parameter (numeric type) and return 2 times its value
x -> 2*x
// 3. Accept 2 parameters (numbers) and return their difference
(x, y) -> xy
// 4. Receive 2 int type integers and return their sum
(int x, int y) -> x + y
// 5. Accept a string object and print it on the console without returning any value (it looks like it returns void)
(String s) -> System.out.print(s)
Basic Lambda example
Now that we know what lambda expressions are, let's start with some basic examples. In this section, we will see how lambda expressions affect the way we code. Suppose there is a List of players, the programmer can use a for statement ("for loop") to traverse, which can be converted to another form in Java SE 8:
Copy the code code as follows:
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);
//Previous loop method
for (String player : players) {
System.out.print(player + "; ");
}
//Use lambda expressions and functional operations
players.forEach((player) -> System.out.print(player + "; "));
//Use 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 be replaced by lambda expressions. Similarly, it can also be used like this when implementing the Runnable interface:
Copy the code code as follows:
//Use anonymous inner class
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!"));
The following is an example of using lambdas to implement the Runnable interface:
Copy the code code as follows:
// 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 lambda expression
Runnable race2 = () -> System.out.println("Hello world!");
// Call the run method directly (no new thread is opened!)
race1.run();
race2.run();
Runnable's lambda expression uses block format to convert five lines of code into a single line statement. Next, in the next section we will use lambdas to sort the collection.
Sorting collections using Lambdas
In Java, Comparator class is used to sort collections. In the example below, we will be based on the player's name, surname, name length and last letter. As in the previous example, we first use an anonymous inner class to sort, and then use lambda expressions to streamline our code.
In the first example, we will sort the list by name. Using the old way, the code would look like this:
Copy the code code as follows:
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 according to name
Arrays.sort(players, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return (s1.compareTo(s2));
}
});
Using lambdas, the same functionality can be achieved with the following code:
Copy the code code as follows:
// 1.2 Use lambda expression to sort players
Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));
Arrays.sort(players, sortByName);
// 1.3 can also take the following form:
Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2)));
Other rankings are as follows. Like the above example, the code implements Comparator through anonymous inner classes and some lambda expressions:
Copy the code code as follows:
// 1.1 Use anonymous inner classes to sort players based on 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 to 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 like this, I wonder if the original author made a mistake, there are so many brackets...
Arrays.sort(players, (String s1, String s2) ->
( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) )
);
// 2.1 Use anonymous inner classes to sort players according to 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 to 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 an 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 Use lambda expression to sort according to the last letter
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 of lambdas' capabilities and use them with streams.
Using Lambdas and Streams
Stream is a wrapper for collections and is usually used together with lambda. Using lambdas can support many operations, such as map, filter, limit, sorted, count, min, max, sum, collect, etc. Similarly, Streams use lazy operations, they do not actually read all the data, and the chain syntax ends when encountering methods like getFirst(). In the following examples, we'll explore what lambdas and streams can do. We created a Person class and used this class to add some data to the list, which will be used for further streaming operations. Person is just a simple POJO class:
Copy the code code as follows:
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 will create two lists, both used to store Person objects:
Copy the code code as follows:
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 and output the above list:
Copy the code code as follows:
System.out.println("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 use the forEach method to increase the programmer's salary by 5%:
Copy the code code as follows:
System.out.println("Give programmers a 5% salary increase:");
Consumer<Person> giveRaise = e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary());
javaProgrammers.forEach(giveRaise);
phpProgrammers.forEach(giveRaise);
Another useful method is filter(), which allows us to display PHP programmers with a monthly salary of more than $1400:
Copy the code code as follows:
System.out.println("Here are PHP programmers with a monthly salary of more than $1,400:")
phpProgrammers.stream()
.filter((p) -> (p.getSalary() > 1400))
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
We can also define filters and then reuse them to perform other operations:
Copy the code code as follows:
// define 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("Here are female PHP programmers who are older than 24 years old and have a monthly salary of more than $1,400:");
phpProgrammers.stream()
.filter(ageFilter)
.filter(salaryFilter)
.filter(genderFilter)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
//Reuse filters
System.out.println("Female Java programmers older than 24 years old:");
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:
Copy the code code as follows:
System.out.println("The first 3 Java programmers:");
javaProgrammers.stream()
.limit(3)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
System.out.println("The top 3 female Java programmers:");
javaProgrammers.stream()
.filter(genderFilter)
.limit(3)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
What about 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:
Copy the code code as follows:
System.out.println("Sort by name and display the top 5 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("Sort Java programmers according to salary:");
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, what is faster than selecting the first/last after sorting is the min and max methods:
Copy the code code as follows:
System.out.println("The lowest 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("Java programmer with the highest salary:");
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. In conjunction with the map method, we can use the collect method to put our result set into a String, a Set or a TreeSet:
Copy the code code as follows:
System.out.println("Concatenate the first name of PHP programmers into a string:");
String phpDevelopers = phpProgrammers
.stream()
.map(Person::getFirstName)
.collect(joining(" ; ")); // Can be used as a token in further operations
System.out.println("Save the first name of Java programmers to Set:");
Set<String> javaDevFirstName = javaProgrammers
.stream()
.map(Person::getFirstName)
.collect(toSet());
System.out.println("Save the first name of Java programmers to TreeSet:");
TreeSet<String> javaDevLastName = javaProgrammers
.stream()
.map(Person::getLastName)
.collect(toCollection(TreeSet::new));
Streams can also be parallel. Examples are as follows:
Copy the code code as follows:
System.out.println("Calculate all money paid to Java programmers:");
int totalSalary = javaProgrammers
.parallelStream()
.mapToInt(p -> p.getSalary())
.sum();
We can use the summaryStatistics method to obtain various summary data of elements in the stream. Next, we can access these methods such as getMax, getMin, getSum or getAverage:
Copy the code code as follows:
//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("The largest number in the List: " + stats.getMax());
System.out.println("The smallest number in the List: " + stats.getMin());
System.out.println("Sum of all numbers: " + stats.getSum());
System.out.println("Average of all numbers: " + stats.getAverage());
OK, that's it, hope you like it!
Summarize
In this article, we learned different ways of using lambda expressions, from basic examples, to more complex examples using lambdas and streams. In addition, we also learned how to use lambda expressions and the Comparator class to sort Java collections.
Translator's Note: Although it looks very advanced, the essence of a Lambda expression is just a "syntax sugar" that is inferred by the compiler and helps you convert and wrap it into regular code, so you can use less code to achieve the same function. . I recommend not to use it indiscriminately, because it is just like the code written by some very advanced hackers. It is concise, difficult to understand, and difficult to debug, and the maintenance staff will want to scold you.