Use Lambda expressions to improve the readability and maintainability of Java code

introduction

Java is a powerful programming language that provides rich features and APIs to meet different types of application needs. However, in actual development, we often find that the Java code is too long and complicated to understand and maintain. At this time, we need to use some modern programming techniques and methods to improve the readability and maintainability of the code. Lambda expression is a new feature introduced by Java 8, which can help us simplify code and improve development efficiency. This article will introduce the principles, usage and best practices of Lambda expressions to help you better apply Lambda expressions to improve the readability and maintainability of Java code.

1. The basic principle of Lambda expression

Before Java 8, we usually use anonymous inner classes to implement functional programming. An anonymous inner class is a lightweight class definition, which is usually used to create a class instance with only one method. For example, the following code creates an anonymous inner class that implements the Comparator interface for sorting strings by length:

List<String> list = Arrays.asList("aaa", "bb", "c");
Collections.sort(list, new Comparator<String>() {
    public int compare(String s1, String s2) {
        return Integer.compare(s1.length(), s2.length());
    }
});

The logic of this code is to compare the lengths of the strings and sort them in ascending order of length. Among them, the Comparator interface defines a compare method for comparing the size of two objects. The anonymous inner class implements this interface and overrides the compare method for easy sorting.

However, this notation has some disadvantages. First of all, the syntax of anonymous inner classes is lengthy and complicated, which can easily lead to poor code readability. Second, an anonymous inner class is usually just a syntactic sugar, it doesn't really encapsulate the code into a function. This means that we cannot perform parameterization, composition, etc. operations on anonymous inner classes like ordinary functions.

The emergence of Lambda expressions is to solve these problems. A lambda expression can be regarded as an anonymous function, which does not require a class definition, and is directly represented by a statement. For example, we can use Lambda expressions to implement the above code:

List<String> list = Arrays.asList("aaa", "bb", "c");
Collections.sort(list, (s1, s2) -> Integer.compare(s1.length(), s2.length()));

The logic of this code is the same as the previous code, except that a Lambda expression is used to simplify the definition of the comparison function. Among them, ​(s1, s2) -> Integer.compare(s1.length(), s2.length())​represents a function that accepts two parameters s1 and s2 and returns the comparison result of their lengths.

The syntax of a lambda expression consists of three parts: the parameter list, the arrow notation, and the function body . Among them, the parameter list and the arrow symbol define the input of the Lambda expression, and the function body defines the output of the Lambda expression. The type of lambda expression is determined by its context. In the above example, the type of the Lambda expression is Comparator<String> because it is passed to the Collections.sort method.

Another advantage of Lambda expressions is that it can perform some compound operations, such as combination, filtering, mapping, etc. For example, we can filter and map a list of strings using Lambda expressions:

List<String> list = Arrays.asList("aaa", "bb", "c");
List<String> filtered = list.stream()
        .filter(s -> s.length() > 1)
        .map(s -> s.toUpperCase())
        .collect(Collectors.toList());

The logic of this code is: first filter out the strings whose length is less than or equal to 1, then convert the remaining strings to uppercase and store them in a new list. Among them, the stream method converts a list into a stream, the filter method is used to filter elements, the map method is used to convert elements into new values, and the collect method is used to collect elements in the stream into a list.

Through Lambda expressions, we can easily implement complex operations without writing a lot of lengthy code. Below we will introduce how to use Lambda expressions to improve the readability and maintainability of Java code.

2. Use Lambda expressions to improve the readability and maintainability of Java code

1. Replace the anonymous inner class with a Lambda expression

As mentioned earlier, Lambda expressions can replace some code that uses anonymous inner classes. Using Lambda expressions can reduce the amount of code and make the code more concise and easy to understand. For example, the following code uses an anonymous inner class to create a thread:

new Thread(new Runnable() {
    public void run() {
        System.out.println("Hello, world!");
    }
}).start();

We can use Lambda expressions to replace anonymous inner classes:

new Thread(() -> System.out.println("Hello, world!")).start();

The logic of this code is: create a new thread that outputs a message while running. Among them, ​() -> System.out.println("Hello, world!")​represents a lambda expression without parameters, and its function body is to output a piece of information.

2. Use Lambda expressions to simplify filtering operations

In Java, we often need to filter data. Typically, we use loops to iterate through the data and use if statements to check if certain conditions are met. This can be simplified using Lambda expressions:

List<String> list = Arrays.asList("aaa", "bb", "c");
List<String> filtered = list.stream()
        .filter(s -> s.length() > 2)
        .collect(Collectors.toList());

The logic of this code is the same as the previous code: filter out a string whose length is greater than 2 in a string list, and store it in a new list. Among them, ​s -> s.length() > 2​​​​​represents a Lambda expression with one parameter, and its function body is ​s.length() > 2​​.

3. Use Lambda Expressions to Simplify Mapping Operations

In addition to filtering operations, we often need to perform mapping operations on data. Typically, we use loops to iterate over the data and use some method to transform the data into a new form. For example, the following code converts all strings in a list of strings to uppercase:

List<String> list = Arrays.asList("aaa", "bb", "c");
List<String> mapped = new ArrayList<String>();
for (String s : list) {
    mapped.add(s.toUpperCase());
}

We can simplify this operation using Lambda expressions:

List<String> list = Arrays.asList("aaa", "bb", "c");
List<String> mapped = list.stream()
        .map(s -> s.toUpperCase())
        .collect(Collectors.toList());

The logic of this code is: Convert all strings in a string list to uppercase and store them in a new list. Among them, ​s -> s.toUpperCase()​​​​​represents a Lambda expression with one parameter, and its function body is ​s.toUpperCase()​​.

4. Use Lambda Expressions to Simplify Sorting Operations

In Java, we often need to sort data. Typically, we use sorting algorithms to sort data. For example, the following code uses the Collections.sort method to sort a list of strings:

List<String> list = Arrays.asList("ccc", "aa", "b");
Collections.sort(list, new Comparator<String>() {
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
});

We can simplify this operation using Lambda expressions:

List<String> list = Arrays.asList("ccc", "aa", "b");
Collections.sort(list, (s1, s2) -> s1.length() - s2.length());

The logic of this code is the same as the previous code: sort a list of strings, sorted by the length of the strings. Among them, ​(s1, s2) -> s1.length() - s2.length()​​​​​represents a Lambda expression with two parameters, and its function body is ​s1.length() - s2.length()​​.

5. Use Lambda expressions to implement callback functions

In Java, we often need to use callback functions to achieve certain functions. Usually, we need to define an interface, implement a method in it, and then pass an instance of the implementation class to a method so that the method can be called when needed. For example, the following code defines an interface and an implementing class, and passes an instance of the implementing class to a method:

interface Callback {
    void call(String message);
}

class MyClass {
    void doSomething(Callback callback) {
        // 做一些事情
        callback.call("完成了");
    }
}

class MyCallback implements Callback {
    public void call(String message) {
        System.out.println("回调函数被调用,消息为:" + message);
    }
}

MyClass myClass = new MyClass();
myClass.doSomething(new MyCallback());

We can simplify this operation using Lambda expressions:

interface Callback {
    void call(String message);
}

class MyClass {
    void doSomething(Callback callback) {
        // 做一些事情
        callback.call("完成了");
    }
}

MyClass myClass = new MyClass();
myClass.doSomething(message -> System.out.println("回调函数被调用,消息为:" + message));

6. Using Lambda expressions to implement threads

In Java, we can use threads to perform asynchronous tasks. Normally, we need to define a class and implement the Runnable interface, then create an instance of this class and pass it to the constructor of the Thread class to create threads when needed. For example, the following code defines a class that implements the Runnable interface, then creates an instance of that class and passes it to the constructor of the Thread class:

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Hello, World!");
    }
}

Thread thread = new Thread(new MyRunnable());
thread.start();

We can simplify this operation using Lambda expressions:

Thread thread = new Thread(() -> System.out.println("Hello, World!"));
thread.start();

The logic of this code is the same as the previous code: define a class and implement the Runnable interface, then create an instance of this class and pass it to the constructor of the Thread class to create threads when needed. Among them, ​() -> {...}​represents a Lambda expression, and its function body is a code block used to output "Hello, World!".

7. Using Lambda expressions to implement comparators

In Java, we often need to sort data. Usually, we need to implement a Comparator interface to define the collation, and then pass it to the sort method. For example, the following code defines a class and a Comparator implementation class, and uses the Comparator implementation class to sort data:

class Person {
    String name;
    int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

class AgeComparator implements Comparator<Person> {
    public int compare(Person p1, Person p2) {
        return p1.age - p2.age;
    }
}

List<Person> persons = new ArrayList<>();
persons.add(new Person("Alice", 18));
persons.add(new Person("Bob", 20));
persons.add(new Person("Charlie", 25));
persons.add(new Person("David", 30));
persons.sort(new AgeComparator());

We can simplify this operation using Lambda expressions:

List<Person> persons = new ArrayList<>();
persons.add(new Person("Alice", 18));
persons.add(new Person("Bob", 20));
persons.add(new Person("Charlie", 25));
persons.add(new Person("David", 30));
persons.sort((p1, p2) -> p1.age - p2.age);

The logic of this code is the same as the previous code: a class and a Comparator implementation class are defined, and the Comparator implementation class is used to sort the data. Among them, ​(p1, p2) -> {...}​represents a Lambda expression, and its function body is a code block, which is used to compare the age property of two Person objects.

8. Use Lambda expressions to realize the conversion of collections

In Java, we can use the Stream API to perform various operations on collections. Among them, a common operation is to convert one collection into another collection. For example, the following code defines a class and a List instance, and uses the Stream API to convert that List instance into another List instance:

class Person {
    String name;
    int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

List<Person> persons = new ArrayList<>();
persons.add(new Person("Alice", 18));
persons.add(new Person("Bob", 20));
persons.add(new Person("Charlie", 25));
persons.add(new Person("David", 30));

List<String> names = new ArrayList<>();
for (Person person : persons) {
    names.add(person.name);
}

We can simplify this operation using Lambda expressions:

List<Person> persons = new ArrayList<>();
persons.add(new Person("Alice", 18));
persons.add(new Person("Bob", 20));
persons.add(new Person("Charlie", 25));
persons.add(new Person("David", 30));

List<String> names = persons.stream().map(person -> person.name).collect(Collectors.toList());

The logic of this code is the same as the previous code: a class and a List instance are defined, and the Stream API is used to convert the List instance into another List instance. Among them, ​person -> person.name​represents a Lambda expression, and its function body is a block of code, which is used to convert the Person object to its name property. ​map​What the method ​collect​The function of the method

9. Using Lambda Expressions to Implement Functional Interfaces

In Java, a functional interface is an interface that contains only one abstract method. Lambda expressions can easily implement functional interfaces. For example, the following code defines a functional interface and a method that uses that interface:

@FunctionalInterface
interface MyFunction {
    void apply(String s);
}

void processString(String s, MyFunction f) {
    f.apply(s);
}

We can implement this functional interface using a Lambda expression, passed to ​processString​the method :

processString("Hello, World!", s -> System.out.println(s));

​The logic of this code is the same as the previous code: a functional interface is defined and a method that uses that interface is defined. Among them, ​s -> System.out.println(s)​represents a Lambda expression, and its function body is a code block used to output a string. We pass that Lambda expression to ​processString​the method , which invokes the Lambda expression's ​apply​method, passing a string parameter. The function body of the Lambda expression outputs the parameter to the console.

Summarize

In this article, we introduced the concept and syntax of Lambda expressions, and demonstrated the application of Lambda expressions through practical code examples.

Guess you like

Origin blog.csdn.net/bairo007/article/details/132338702