In-depth knowledge of Lambda

JDK 8 is arguably the biggest change since the release of Java, and lambda is one of a series of changes that can be said to make Java a stuffy, middle-aged man into a greasy man, less stuffy and more flexible. This is also a riposte to the notion that Java syntax is cumbersome and inflexible.

The following table of contents is to be eaten on demand.

  • In-depth knowledge of Lambda
  • What is Lambda, and what problem does it solve?
  • Basic concept of Lambda
    • grammar
    • Functional interface
    • Method and constructor references
      • Static method reference
      • Instance method reference
  • Lambda parameters are behaviorized
  • conclusion
  • Functional interface appendix

What is Lambda, and what problem does it solve?

Before we get to Lambda, we need to review the concept of anonymous inner classes:

Classes that implement an interface without a name become anonymous inner classes. It is usually created in a method using the new keyword. In general, this class does not have overly complex aggregation logic and is designed to solve a single logical problem, such as sorting or threading.

Let’s take a look at the evolution of Java handling this scenario with an example.

  • The inner class
private static class IntegerComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer a, Integer b) {
        return b - a;
    }
}

List<Integer> list = Arrays.asList(1.2.3.4.5.6.7.8.9);
Collections.sort(list, new IntegerComparator());

log.info("Sorted of list: {}", list);
Copy the code

Normally we don’t use it this way, because IntegerComparator is a one-time use with no reuse requirements, so it’s a little clunky, and that’s where anonymous inner classes come in

  • Anonymous inner class
List<Integer> list = Arrays.asList(1.2.3.4.5.6.7.8.9);
Collections.sort(list, new Comparator<Integer>() {
    @Override
    public int compare(Integer a, Integer b) {
        returnb - a; }}); log.info("Sorted of list: {}", list);
Copy the code

But the inlining of the code doesn’t reduce the amount of code per se compared to the internal static class, so this is the main reason why Lambda was introduced.

  • Lambda
 List<Integer> list = Arrays.asList(1.2.3.4.5.6.7.8.9);
//list.sort(Integer::compareTo);
list.sort((a, b) -> b - a);

log.info("Sorted of list: {}", list);
Copy the code

It can be seen that before Lambda syntax, Java has been optimizing the experience of using anonymous inner classes. However, after borrowing the syntax features of other languages, Lambda expressions are more thorough, which greatly improves the experience of writing and reading by eliminating sample code and only retaining logical code.

So Lambda mainly improves the writing experience of anonymous inner classes.

Basic concept of Lambda

In mathematics and computer science, a Lambda expression (also called a higher-order function on Wikipedia) is a function that satisfies at least one of the following conditions:

  1. Take one or more functions as input
  2. Output a function

Before Java 8, there was no way to write a single function, and methods were often used instead of functions, but they were always part of an object or method. Lambda expressions now offer a closer approach to the concept of independent functions. As an alternative to anonymous inner classes, it has the following advantages:

  1. Syntax brief is compact and allows for omissionAccess modifier,The return type,The parameter typesAnd so on.
  2. Parameter behavior, improve code reuse
  3. Plenty out of the boxFunctional tool, to bring Lambda out-of-the-box tools for common scenarios

grammar

Lambda expressions consist of argument lists and method bodies, separated by ->, so the following are legal

// Single parameter
p -> p.translate(1.1)
i -> new Point(i, i+1)


// One or more parameters, denoted by (), are consistent with ordinary methods() - >16
(x, y) -> x +y

// Set the parameter type, usually omitted because it can be inferred from the method declaration of its implementation,
(int x, int y) -> x +y
// JDK 11 enhanced var to be used in lambda
(var x, var y) -> x +y
Copy the code

If we express lambda expressions in abstract syntax, it looks like this:

args -> expr

// Sometimes if the method returns a value and the implementation logic is too complex to use a single line representation, you can write it like this
args -> { returnexpr; }Copy the code

Functional interface

To use lambda representations, you must inherit functional interfaces, that is, interfaces that have only one abstract method. When an interface meets such conditions, you can use lambda expressions. In JDK 8, the @functionalinterface annotation was added to an interface to indicate that an interface is a FunctionalInterface. We can see the functional interfaces of JDK 8 out of the box in the java.util.function package.

The function interface expression explain
Function<T, R> {R apply(T t); } T -> R Accept the parameter T and generate the result R
BiFunction<T, U, R> {R apply(T t, U u); } (T, U) -> R Take the parameters T,U, and generate the result R
Predicate{ boolean test(T t); } T ->Boolean Accepts the argument T and returns a Boolean value
Supplier{ T get(); } () ->T Producer model
Consumer{ void accept(T t); } T ->() Consumer model

In addition to these functional interfaces, there are many other derivative interfaces, most of which are specifically created to improve the performance of primitive type parameters. Here’s a look at the Supplier and Consumer usage scenarios to get an idea of how Lambda is designed.

  • Consumer
 
@Slf4j
public class ConsumerDemo {
    public static void main(String[] args) {
        // Prints parameters
        Consumer<String> printer = t -> System.out.println(t);
        // As the Consumer documentation states, it operates through side effects.
        // All elements in the collection are processed through side effects
        Consumer<List<Integer>> odd = list -> {
            for (int i = 0; i < list.size(); i++)
                list.set(i, 2 * list.get(i));
        };

        printer.accept("Hello,");
        // Can be passed as a parameter to other methods, which can greatly improve and compensate for the reusability of existing methods.
        debug("Word", printer);

        var list = Arrays.asList(1.2.3.4.5.6.7.8.9);
        odd.accept(list);
        log.info("{}", list);

    }

    public static void debug(String msg, Consumer<String> consumer) { consumer.accept(msg); }}Copy the code
  • The Supplier example uses Supplier to create a random number generator
@Slf4j
public class SupplierDemo {
    //RandomGenerator
    public static final Random random = new Random(10086L);

    public static void main(String[] args) {
        // Prints random numbers
        Consumer<Integer> printer = t -> log.info("{}", t);
        // Generate a random number
        Supplier<Integer> randomGenerator = () -> random.nextInt(0.100);

        // Use it directly
        printer.accept(randomGenerator.get());
        // Through a method call
        printer.accept(randomInt(randomGenerator));
        printer.accept(randomInt(randomGenerator));
    }

    public static Integer randomInt(Supplier<Integer> randomGenerator) {
        returnrandomGenerator.get(); }}Copy the code

Method and constructor references

Lambda is enough of an introduction to anonymous inner classes, but we can be even more succinct

/ / print
Consumer<List<Integer>> printer = t -> System.out.println(t);
// Method reference
Consumer<List<Integer>> printer = System.out::println;

var list = Arrays.asList(1.2.3.4.5.6.7.8.9);
// Sort the list from smallest to largest
list.sort((o1, o2) -> o1- o2);
// Method reference
list.sort(Integer::compare);
Copy the code

In the code, we do find that Lambda expressions are much more concise, and this notation has a name, called method references, that was introduced with JDK 8. Method references can be divided into four categories.

The name of the grammar Lambda is equivalent to hair
Static method reference RefType::staticMethod (args)->RefType.staticMethod(args)
Instance method reference expr::instMethod (args)->expr.instMethod(args)
Unbound case method references RefType::staticMethod (arg0, rest)-> arg0.instMethod(rest)
Constructor reference ClsName::new (args)-> new ClsName(args)

Static method reference

Static method references only need to separate class and static method() with **::**, for example


ToIntFunction<String> toInt = str -> Integer.valueOf(str);
// Method reference
ToIntFunction<String> toInteger = Integer::valueOf;


var list = Arrays.asList(1.2.3.4.5.6.7.8.9);
list.sort((o1, o2) -> o2 - o1);
// Method reference
list.sort(Integer::compare);
Copy the code

Instance method reference

Power method references are divided into bound instance method references and unbound instance method references.

Binding instance method references, and was very close to a static method, use ObjectReference: : Identifier instead of ReferType: : Identifier

str -> System.out.print(str)
// Method reference
System.out::print
Copy the code

Unbound methods that do not bind a sink in context (the System.out object in the above example)

user -> user.getName()
// Method reference
user::getName
Copy the code

Constructor reference

Stream<String> stringStream = Stream.of("a.txt"."b.txt"."c.txt");

Stream<File> fileStream = stringStream.map(str-> new File(str))
// Reference methods
Stream<File> fileStream = stringStream.map(File::new);
Copy the code

Lambda parameters are behaviorized

Before Java 8, method arguments could not be passed as methods, but only as states. Therefore, many code with similar behavior could not be reused, resulting in a lot of code duplication. The introduction of Lambda solved this problem by abstracting repetitive and similar code logic into well-designed and implemented functional interfaces. Reuse of methods can be achieved.

Here we use article publishing as a final comprehensive example to show the further logic taken by behaviorization of Lambda arguments.


@Slf4j
public class PredicateDemo {
    // No longer needed
    public void publish(Long articleId) {
        var article = get(articleId);
        if (Objects.equals(article.getPublished(), Boolean.FALSE)) {
            log.info("Publish article: {}", article.getId());
        } else {
            // Error log or just omit}}// No longer needed
    public void unpublish(Long articleId) {
        var article = get(articleId);
        if (Objects.equals(article.getPublished(), Boolean.TRUE)) {
            log.info("UnPublish article: {}", article.getId());
        } else {
            // Error log or just omit}}// The parameterization of Lambda opens up the possibility of unification at the API implementation level
    // Predicate handles the publishing and unpublishing logic of articles, as well as exception handling
    public void doPublish(Long articleId, Boolean status, Predicate<Article> predicate) {
        var article = get(articleId);
        if (predicate.test(article)) {
            article.setPublished(status);
            log.info("Set article status to :{}", status);
        } else {
            log.error("Illegal Status of Article {} to set:{} ", article, status); }}public static void main(String[] args) {
        PredicateDemo predicateDemo = new PredicateDemo();
        // Correct test cases
        predicateDemo.doPublish(1L, Boolean.TRUE, p -> Objects.equals(p.getPublished(), Boolean.FALSE));
        predicateDemo.doPublish(2L, Boolean.FALSE, p -> Objects.equals(p.getPublished(), Boolean.TRUE));
        // Failed test case
        predicateDemo.doPublish(1L, Boolean.TRUE, p -> Objects.equals(p.getPublished(), Boolean.TRUE));
        predicateDemo.doPublish(2L, Boolean.FALSE, p -> Objects.equals(p.getPublished(), Boolean.FALSE));
    }

    public Article get(Long id) {
        return findAll().get(id);
    }

    public static Map<Long, Article> findAll(a) {
        return Map.of(
                1L.new Article(1L.1 "test".false),
                2L.new Article(2L."The test 2".true)); }}@Getter
@Setter
@AllArgsConstructor
@ToString
class Article {
    private Long id;
    private String name;
    private Boolean published;
}
Copy the code

conclusion

All that is left is that you need to understand the way of thinking of Lambda, be familiar with all functional interfaces under java.util.function, and then use them in a small range in your own projects to understand and master the way of thinking of Lambda. Can significantly increase the joy of programming in Java.

Also, the introduction of Streams in Java 8 is possible due to the huge improvements Lambda expressions offer in improving anonymous inner classes, but that’s a topic for another day.

Functional interface appendix

Current interface Perferred interface
Function<Integer, R> IntFunction
Function<Long, R> LongFunction
Function<Double, R> DoubleFunction
Function<Double,Integer> DoubleToIntFunction
Function<Double,Long> DoubleToLongFunction
Function<Long,Double> LongToDoubleFunction
Function<Long,Integer> LongToIntFunction
Function<R,Integer> ToIntFunction
Function<R,Long> ToLongFunction
Function<R,Double> ToDoubleFunction
Function<T,T> UnaryOperator
BiFunction<T,T,T> BinaryOperator
Consumer IntConsumer
Consumer DoubleConsumer
Consumer LongConsumer
BiConsumer<T,Integer> ObjIntConsumer
BiConsumer<T,Long> ObjLongConsumer
BiConsumer<T,Double> ObjDoubleConsumer
Predicate IntPredicate
Predicate DoublePredicate
Predicate LongPredicate
Supplier IntSupplier
Supplier DoubleSupplier
Supplier LongSupplier
Supplier BooleanSupplier
UnaryOperator IntUnaryOperator
UnaryOperator DoubleUnaryOperator
UnaryOperator LongUnaryOperator
BinaryOperator IntBinaryOperator
BinaryOperator LongBinaryOperator
BinaryOperator DoubleBinaryOperator
Function<T, Boolean> Predicate
BiFunction<T,U,Boolean> BiPredicate<T,U>