over 4 years ago

Finally, Java 8 released at March 18, 2014. However, after I developing iOS App in the company, I did not often use Java and had no time to use the beta version of Java 8. Recently, I'm interested in Java 8 and study its new features. One of the highlighted features of Java 8 is the Lambda expression. The Lambda expression can be seen in many languages. I wrote programs in the Lambda expression form most with JavaScript and Objective C (as known as code block). Honestly, I don't like the Lambda very much. I feel okay with the Lambda expression in JavaScript (by passing named function), but I feel the in-place code block of Objective C like spaghetti. In most cases, I use methods that return a code block to keep the program well-structured.

Well, Lambda becomes a part of Java 8, and what changes will make in writing Java programs? Let's see the program before Java 8, sorting a list as an example, to sort a List, the programmer needs to write a class that implements Comparator interface (the IntegerComparator in Code List 1), and then create an instance ascendingComparator as the parameter of List.sort() (see the sortWithComparator method in Code List 2). This is why many programmers say that Java is less productivity. However, I like to program in this way -- writing a lot of small classes because a small class is easy to write, easy to test with JUnit, and easy to reuse.

If the programmer do not want to write a class independently, he/she can write an anonymous class before Java 8, like the sortWithAnonymousClass method in Code List 2. I used this way to write programs at the beginning of learning Java with IDE like JBuilder -- drag-and-drop to design a UI, double-click on the control, and write some programs at the place that IDE auto-generated (yes, I learned the Visual Basic 6 in a similar way). After I learned the formal object-oriented analysis and design, I only use this way in the case that the anonymous class does not affect the overall design. However, many programmers still think the way is less productivity.

Code List 1 - IntegerComparator
package java8.lambdas;

import java.util.Comparator;

public class IntegerComparator implements Comparator<Integer> {

    private boolean _ascending;

    public IntegerComparator() {
        this(true);
    }

    public IntegerComparator(boolean ascending) {
        _ascending = ascending;
    }

    @Override
    public int compare(Integer number1, Integer number2) {
        return _ascending? number1.compareTo(number2) : number2.compareTo(number1);
    }
}

With the Lambda expression in Java 8, the sorting program can be written like the sortWithLambdaExpression method in Code List 2. The lines of code reduced and many programmers think that the readability also improved. The reason is that the sorting implementation is right there, you don't have to jump to another class (file) to read the implementation. I think that the readability improved only when the the logic wrapped by anonymous Lambda is very simple. If the logic is very complicated, put two different logics (logic outside the Lambda and the logic inside the Lambda) together only increase the length of code to read and reduce the readability. In that case, a better way is to extract the logic in the Lambda to a meaningful class. I'm used to the form: (arguments) -> {implementation} to write an anonymous Lambda -- I think that is because I had seen a lot of code blocks with Objective C.

Besides anonymous Lambda, Java 8 also support to pass the existing method as the Lambda, called method reference . For example, the sortWithMethodReference and sortWithStaticMethodReference methods in Code List 2. Comparing the sortWithLambdaExpression and sortWithMethodReference methods, which one is more readable? In my personal opinion, I think sortWithMethodReference is more readable than sortWithLambdaExpression because the method name can tell me that the numbers is sorted from small to large. I usually write Lambda with JavaScript in this way -- passing a meaningful named function.

Code List 2 - LambdaExample
package java8.lambdas;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class LambdaExample {

    public static void main(String[] arguments) {
        if(arguments.length == 0) {
            System.err.print("Nothing to sort. Abort!");
        }
        else
        {
            LambdaExample lambdas = new LambdaExample();
            List<Integer> numbers = new ArrayList<Integer>();
            for(String argument : arguments) {
                numbers.add(Integer.parseInt(argument));
            }
            lambdas.sortWithComparator(new ArrayList(numbers));
            lambdas.sortWithAnonymousClass(new ArrayList(numbers));
            lambdas.sortWithLambdaExpression(new ArrayList(numbers));
            lambdas.sortWithMethodReference(new ArrayList(numbers));
            lambdas.sortWithStaticMethodReference(new ArrayList(numbers));
        }
    }

    public void sortWithComparator(List<Integer> numbers) {
        IntegerComparator ascendingComparator = new IntegerComparator();
        numbers.sort(ascendingComparator);
        print("sort with comparator", numbers);
    }

    public void sortWithAnonymousClass(List<Integer> numbers) {
        numbers.sort(new Comparator<Integer>() {

            @Override
            public int compare(Integer number1, Integer number2) {
                return number1.compareTo(number2);
            }
        });
        print("sort with inner class", numbers);
    }

    public void sortWithLambdaExpression(List<Integer> numbers) {
        numbers.sort((number1, number2) -> number1.compareTo(number2));
        print("sort with Lambda", numbers);
    }

    public void sortWithMethodReference(List<Integer> numbers) {
        numbers.sort(this::compareAscending);
        print("sort with method reference", numbers);
    }

    public void sortWithStaticMethodReference(List<Integer> numbers) {
        numbers.sort(Integer::compare);
        print("sort with static method reference", numbers);
    }

    public int compareAscending(Integer number1, Integer number2) {
        return number1.compareTo(number2);
    }

    private void print(String approach, List<Integer> numbers) {
        System.out.println(approach);
        for(Integer number : numbers) {
            System.out.print(String.format("%d ", number));
        }
        System.out.println();
    }
}

Someone may say that what about closure? I want to discuss the closure in the next article. This article only discussed the productivity and the readability affected by Lambda. For the readability, as mentioned before, simple logic wrapped with Lambda indeed improves the reabability, but put complicated logic wrapped with Lambda and the other logic together will reduce the readability. For productivity, Lambda can write less codes (the basic requirement of a class declaration). Therefore, when the wrapped logic is simple, the productivity improvement is significant. For example, in Code List 1, the compare method only has one line, to declare a class for that line costs a lot. However, if the wrapped logic is complicated, the productivity improvement is not so important.

In the long term, writing less codes may not improve the productivity, but well-testable codes improve the productivity. This is also the reason I use methods to return code blocks when I developing iOS App. I believe that increasing the testability of a program can improve much more productivity than writing less codes because the time to maintain an existing program is longer than that to write new codes. As shown in Code List 3, doSomething may be an integration method and an anonymous Lambda wrapped logic inside the method. In order to test the wrapped logic, the only way is to test the entire doSomething method. If the logic is wrapped as independent class like IntegerComparator, it is easy to test the logic by test IntegerComparator directly without testing the integration method doSomething. For testability, using method reference is a better way than using anonymous Lambda.

Code List 3 - Testability
public void doSomething() {
    List<Integer> numbers = complexPreparation();
    numbers.sort((number1, number2) -> number1.compareTo(number2));
    complexPostProcess(numbers);
}

Someone may think the sample code in Code List 3 is not a good example. There are other ways to improve the testability for Code List 3 indeed, but, I still think the anonymous Lambda does not provide a good testablity. Therefore, I prefer the method reference way to write Lambda with Java 8. Besides testability, I can follow the OO principles, e.g., single responsibility principle, to put the methods in the appropriate classes, and then use the method reference to use the methods as Lambda. As a result, the method reference approach increases readability (with meaningful method name), maintainability (good OO desing and easy to test) and also reusability (anonymous Lambda can not be reused, but method reference can reuse methods).

← Java 8 初探 - Lambda Java 8 初探 - Closure →