java8 lambda expression

Several forms of lambda expressions

        Runnable runnable = () -> System.out.println("Hello World");
        
        Runnable runnable1 = () -> {
            System.out.println("Hello");
            System.out.println("World");
        };
        
        ActionListener listener = event -> System.out.println("button clicked");
        
        BinaryOperator<Long> add = (x, y) -> x + y;
        
        BinaryOperator<Long> add1 = (Long x, Long y) -> x + y;

Functional programming instead of imperative programming

The java language has supported imperative and object-oriented programming since its first version, and in java8 a powerful functional programming syntax has been added, which is also generally more concise, expressive, and easier to parallelize. So it is necessary for us to learn functional programming. Although learning new programming ideas takes a lot of time, it is worth the investment relative to the return. Let's take a look at the syntax differences between imperative and functional programming.

        public int externalCountStudentFromShannxi(List<Student> students){
            int count = 0;
            for(Student student : students) {
                if(student.isFrom(Constants.Provinces.SHANNXI)) {
                    count++;
                }
            }
            return count;
        }

This kind of code is familiar to everyone. First define a temporary variable count, then loop through the list of students, find students from Shaanxi, add 1 to count, and finally return count. The imperative format provides us with complete control, which sometimes is a good thing. On the other hand, you need to perform all the work. In many cases, the workload can be reduced to increase efficiency.

        public long innerIterator(List<Student> students) {
            return students
                    .stream()
                    .filter(student -> student.isFrom(Constants.Provinces.SHANNXI))
                    .count();
        }

And functional programming is more like a narrative, convert the list of students into a stream, filter, and then count, we just tell the program what we want to do, and the program executes the corresponding result. Such coding omits a lot of intermediate variables, and there is a certain improvement in efficiency.

higher order functions in java

A higher-order function is a function that accepts another function as a parameter, or returns a function. According to the method signature, if there is a function interface in the parameter or return value, then the function is a higher-order function. Take a look at an example below. Count the number of occurrences of each character in a string. The effect of both implementations is obvious.

        public Map<String, Long> accountNumber(String str){
            Objects.requireNonNull(str, "String cannot be empty");
            Map<String, Long> result = new HashMap<String, Long>();
            List<String> list = Arrays.asList(str.split(""));
            for(String string : list) {
                if(!result.containsKey(string)) {
                    result.put(string, 0l);
                    continue;
                }
                result.put(string, result.get(string) + 1);
            }
            return result;
        }
        
        public Map<String, Long> accountNumberWithLambda(String str) {
            Objects.requireNonNull(str, "String cannot be empty");
            return Arrays.asList(str.split("")).stream().collect(groupingBy(string -> string, counting()));
        }
        
        

Write readable code

Although the implementation of the lambda method in the above example is very simple, and the real logic is only one line, it gives me the feeling that lambda is used for the sake of using lambda. We can choose a more elegant way to optimize the code. Keeping the code brief and not blunt, it's easy to overlook the value of expressiveness and readability in programming. Java 8 encourages these qualities by convention, suggesting that we align points in the vertical direction of function composition.

        public Map<String, Long> accountNumberWithLambda(String str) {
            Objects.requireNonNull(str, "String cannot be empty");
            return Arrays
                    .asList(str.split(""))
                    .stream()
                    .collect(groupingBy(string -> string, counting()));
        }

The perfect lambda expression is only one line

Why is a perfect lambda expression only one line, let us understand the following through the code, we need to print the English grades of students from Shaanxi with an even number of students.

        public void printScore(List<Student> students) {
            System.out.println(students.stream()
                    .filter(student -> {
                        boolean isEven = Integer.parseInt(student.getId()) % 2 == 0;
                        boolean isFromShannxi = student.isFrom(Constants.Provinces.SHANNXI);
                        return isEven && isFromShannxi;
                    })
                    .collect(Collectors.toList())
                    .toString()
                    );
        }  

Although such code uses the style of functional programming, it still cannot get rid of the thinking of imperative programming. The code looks a bit different, and the intermediate variables are not less defined, and the intention of the code is difficult to understand. The reason for this is that our lambda expressions are not minimal, and the expressiveness of function composition depends heavily on the brevity of each lambda expression. If your lambda expression contains multiple lines (maybe even two lines is too much). Many times we can use method references to optimize our lambda expressions, let's optimize the code above. As follows:

        System.out.println(
            students.stream()
            .filter(student -> student.isFrom(Constants.Provinces.SHANNXI))
            .filter(student -> (Integer.parseInt(student.getId()) % 2 == 0))
            .collect(Collectors.toList())
            );
            
        System.out.println(
            students.stream()
            .filter(student -> student.isFrom(Constants.Provinces.SHANNXI) && (Integer.parseInt(student.getId()) % 2 == 0))
            .collect(Collectors.toList())
            );

Cascading lambda expressions

If you see x -> y -> x > ycode like this, don't panic, it's nothing, it's all lambda expressions, let's analyze the syntax, imagine there are two lists, numbers1 and numbers2. Suppose we want to extract only from the first list Numbers greater than 50, then extract numbers greater than 50 from the second list and multiply by 2.

        List<Integer> result1 = list1.stream()
            .filter(e -> e > 50)
            .collect(Collectors.toList());
    
        List<Integer> result2 = list2.stream()
            .filter(e -> e > 50)
            .map(e -> e*2 )
            .collect(Collectors.toList());
            

The implementation of the code is simple, but there is still redundancy. For the expression that checks if the number is greater than 50, we use it twice. We can use a Predicate function interface to represent a lambda expression that returns a boolean value, thereby removing duplicate code.

        Predicate<Integer> predicate = e -> e > 50;
        

But this will cause a problem. If I want a value greater than 25, or greater than 75, our lambda has to be rewritten again. It seems to be optimized, but in fact, the amount of code is more. Therefore, we need to pass the parameters of the comparison into the lambda expression. JDK provides us with such an expression Function, which receives T and returns R. Therefore, our code can be rewritten as follows.

        Function<Integer, Predicate<Integer>> function = (Integer num) ->{
        
            Predicate<Integer> isLargeThan = (Integer candidate) -> {
                return candidate > num;
            } ;
            return isLargeThan;
        };
        
        List<Integer> result = list2.stream()
            .filter(function.apply(50))
            .map(e -> e*2 )
            .collect(Collectors.toList());
            

In this way, we can reuse this logic only by changing the parameters of the apply() method. But the code for this still looks messy, so let's start removing redundant logic. First remove the type declaration, java can infer the type we need without type confusion.

        Function<Integer, Predicate<Integer>> function = (Integer num) ->{
        
            Predicate<Integer> isLargeThan = (Integer candidate) -> {
                return candidate > num;
            } ;
            return isLargeThan;
        };
        
        Function<Integer, Predicate<Integer>> function1 = num ->{
        
            Predicate<Integer> isLargeThan = candidate -> {
                return candidate > num;
            } ;
            return isLargeThan;
        };

There are improvements but not much, only two words and two parentheses have been removed. We know that curly braces and return are redundant when the body of a lambda expression is only one line of code. Let's go ahead and delete.

        Function<Integer, Predicate<Integer>> function2 = num ->{
        
            return candidate -> candidate > num;
        };

The body of the outer lambda expression is now only one line left, so the curly braces and return of the outer lambda expression can continue to be removed.

        Function<Integer, Predicate<Integer>> function3 = num -> candidate -> candidate > num;
        
concluding remarks

Finally we see the cascading lambda expression above. It's nothing new. Let's think about the following, if there are too many such expressions in the code, it is estimated that the developer who wrote the code will forget what the expression is trying to do the next day, so this is not the result we want. When there are too many cascading lambda expressions in the code, it is necessary to consider whether such a design is necessary. The code that appears in the article is available for download on GitHub .

related topic

java Stream series
java study guide
About Lambda FAQ
Java 8: Lambdas, Part 1
Cascading lambda expressions

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325301041&siteId=291194637