Java 8 Lambdas and Streams… not a “how” , but a “w

2019-07-26 06:24发布

问题:

This question already has an answer here:

  • Do Java Lambda Expressions Utilize “Hidden” or Local Package Imports? 4 answers

The question I have involves both Lambdas and Streams. There are a couple of things that I can't resolve. Starting with the lambdas, using Predicate as the example.

Notice how in the following code I neither import "java.util.function.Predicate" nor do I implement the Predicate interface in the class declaration. And yet, the Lambda works just fine. Why is that?

public class Using_Predicate {
    public static List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    public static void main(String[] args) {
        // passing the numbers List and different Lambdas to the intermediate
        // function.
        System.out.println();
        printVals(numbers, x -> x > 6); // all values greater than 6
        System.out.println();
        printVals(numbers, x -> x % 2 == 0); // all even values
        System.out.println();
        printVals(numbers, x -> x < 8); // ll values less than 8
        System.out.println();
        printVals(numbers, x -> x % 2 == 1); // all odd values
    }

    //intermediate Predicate function
    public static void printVals(List<Integer> val, Predicate<Integer>
            condition) {
        for (Integer v : val) {
            if (condition.test(v)) // if true, print v
                System.out.print(v + " ");
        }
    }
}

Notice how I have to employ an "intermediate function" that utilizes the "test()" method of the Predicate functional interface. However, if I decide to do something similar in using a stream, I again neither have to import java.util.function.Predicate, or java.util.Stream, or implement the Predicate interface in the class declaration. Furthermore, I can use a Predicate Lambda in the stream without even having to create an intermediate function! Why is that?

For example:

// a predicate lambda that prints the first value greater than 3, in this case 5
public class Sample1 {
    public static void main(String[] args) {
        List<Integer> values = Arrays.asList(1, 2, 3, 5, 4, 6, 7, 8, 9, 10);

        System.out.println(
                values.stream()
                        .filter(e -> e > 3)
                        .findFirst()
        );
    }
}

So, I am really confused on the "why" of the rules for Lambdas and streams, but not so much on the "how".

回答1:

Imports are used mainly to allow you to not have to write the complete package name of a class. So in this case you would need to import Predicate if you use the word Predicate somewhere in your code. If for example you did have to use the word Predicate and instead wrote out the full package name, java.util.function.Predicate, then you would not need to include the import.

Import is not used for providing your code with the public API of that class. So you do not need to import Predicate to create a lambda for Predicate. The compiler already knows which Predicate class is being referred by the filter method.



回答2:

The thing is this a Predicate to you, but not the compiler. if we write just this:

x -> x > 6

this is a Predicate, right? But why not a Function also?

Function<Integer, Boolean> func = x -> x > 6;

Or any other type that might fit here.

This also has to do with the fact that lambda expressions are not build-in per-se (logically like an enum - it's just some complicated sugar) and since you are not directly using Predicate here, the compiler infers this to a be a Predicate.



回答3:

The keyword to search for is Functional Interface.

Every interface that only requires you to implement only a single method (default method implementations are considered to be already implemented) is a functional interface and can be substituted with the lambda notation or a corresponding method reference in Java 8.

Java 8 comes with a bunch of predefined functional interfaces for common tasks (Action, Function, Predicate, Consumer, etc.) and the stream API makes use of them.

The filter() method, for example, takes a Predicate, which is a functional interface. So you can write it as lambda expression or as method reference to a method that takes a single argument and returns a boolean value. The typeparameter T can be inferred from the type of the return value of the previous step in the stream.

The rest works like every other place in Java: if you don't explicitly use the symbol name of a class in your class, you don't need to import it, even if your class calls methods that accept and return that other class.

Think of the implementation of functional interfaces as something akin to anonymous classes, just even more lightweight and with even shorter syntax.