1. Behavior parameterization
The shaded area can be calculated by ∫ sinxdx at (0, Π) The points obtained on the are reflected in the code:
integrate(F(x), 0, pi);
integrate(-cos(x), 0, pi);
Or, if we want to calculate the cosine integral, we need to transfer:
integrate(sin(x), 0, pi);
Of course, we can't pass the original function F(x) directly yet. We can pass it through
Method call:
public static double integrateSine(double a,double b){ return (-Math.cos(b)) - (-Math.cos(a)); } public static double integrateCosine(double a,double b){ return (Math.sin(b)) - (Math.sin(a)); } public static void main(String[] args) { double s = integrateSine(0,Math.PI); System.out.println(s); }
Polymorphism:
public interface PrimitiveFunction{ double getY(double x); } public static class SinPrimitiveFunction implements PrimitiveFunction{ @Override public double getY(double x) { return -Math.cos(x); } } public static class CosPrimitiveFunction implements PrimitiveFunction{ @Override public double getY(double x) { return Math.sin(x); } } public static double integrate(PrimitiveFunction pf, double a,double b){ return pf.getY(b) - pf.getY(a); } public static void main(String[] args) { double s = integrate(new SinPrimitiveFunction(), 0, Math.PI); System.out.println(s); }
Anonymous class:
public interface PrimitiveFunction{ double getY(double x); } public static double integrate(PrimitiveFunction pf, double a,double b){ return pf.getY(b) - pf.getY(a); } public static void main(String[] args) { double s = integrate(new PrimitiveFunction() { @Override public double getY(double x) { return -Math.cos(x); } }, 0, Math.PI); System.out.println(s); }
Step to optimize our code.
However, this is still very cumbersome. What we need to change is only one line (or multiple lines) of code.
It would be great if the code could be passed as a parameter (similar to the pseudo code above).
Before Java 8, it didn't work. Only objects can be passed. How can code be passed directly.
After Java 8, we will change this idea. Lambda expressions allow us to better implement behavior parameterization.
2. Functional interface
Before learning Lambda, let's look at a concept - functional interface
In a word, the interface with only one abstract method is a functional interface (what's the ghost of multiple non abstract methods? It's not shown here for the time being, and will be discussed later). You can mark @ FunctionalInterface to identify and check the compiler
Runnable and Callable are well-known functional interfaces, followed by a large number of functional interfaces provided by Java 8.
Of course, we can also create our own functional interface. The PrimitiveFunction above is a functional interface
3.Lambda expression
After understanding the functional interface, let's take another look at the code, which can be modified as follows:
public interface PrimitiveFunction{ double getY(double x); } public static double integrate(PrimitiveFunction pf, double a,double b){ return pf.getY(b) - pf.getY(a); } public static void main(String[] args) { double s = integrate((double x)->-Math.cos(x), 0, Math.PI); System.out.println(s); }
This is a major breakthrough. We didn't write redundant content, just put - math Cos (x) is passed to the method through a Lambda expression (at least it looks like this, but it actually generates an instance for a functional interface)
4. Type check and inference
How does a Lambda expression work? Type checking is used here.
The target type is defined through the Lambda context (parameters and return values of interface methods). The Lambda expression passed can be compiled only when it conforms to the target type.
For example, in the above code:
According to integrate (primitive function PF, double a, double B), find the target type as primitive function.
The abstract method of PrimitiveFunction , double getY(double x), means to receive a double and return a double at the same time. Here, it can be said that the function descriptor is , double - > double.
Lambda expression (double x) - > - math Cos (x) also receives a double and returns a double, which is consistent with the function descriptor. Check it.
The Java compiler can not only check types through context, but also infer types.
For example, in the above code:
It can be inferred that the required Lambda type is double - > double, so we have no additional definition types. The code can be reduced to:
public interface PrimitiveFunction{ double getY(double x); } public static double integrate(PrimitiveFunction pf, double a,double b){ return pf.getY(b) - pf.getY(a); } public static void main(String[] args) { double s = integrate((x) -> -Math.cos(x),0, Math.PI); System.out.println(s); }
Even when Lambda has only one parameter of type to be inferred, the parentheses around the parameter name can be omitted:
public interface PrimitiveFunction{ double getY(double x); } public static double integrate(PrimitiveFunction pf, double a,double b){ return pf.getY(b) - pf.getY(a); } public static void main(String[] args) { double s = integrate(x -> -Math.cos(x), 0, Math.PI); System.out.println(s); }
5. Common functional interfaces
At this point, we can already apply Lambda expressions in the code, but we have to build a functional interface every time (here is PrimitiveFunction). It's too troublesome!
Java 8 has long thought of this, in Java util. Under the function package, common functional interfaces (predict, consumer, function, etc.) are provided for us
At this point, our code can be modified to:
public static double integrate(Function<Double,Double> pf, double a,double b){ return pf.apply(b) - pf.apply(a); } public static void main(String[] args) { double s = integrate(x -> -Math.cos(x), 0, Math.PI); System.out.println(s); }
At the same time, Java8 also takes into account the performance loss of packing and unpacking. We can replace it with a better functional interface, DoubleFunction:
public static double integrate(DoubleFunction<Double> pf, double a,double b){ return pf.apply(b) - pf.apply(a); } public static void main(String[] args) { double s = integrate(x -> -Math.cos(x), 0, Math.PI); System.out.println(s); }
6. Method reference
Can it be simpler? Method reference can help you achieve!
Whether it's - math Cos (x), math Sin (x) or not, we are essentially passing methods. If a method already exists, should we directly indicate the method.
Here is a syntax sugar (target reference:: method name) to help us achieve this
So... Math Sin (x) is equivalent to Math::sin (note that the sin method is not bracketed, here it only indicates the method, and there is no actual call):
public static double integrate(DoubleFunction<Double> pf, double a,double b){ return pf.apply(b) - pf.apply(a); } public static void main(String[] args) { double s = integrate(Math::sin, 0, Math.PI); System.out.println(s); }
Where can I use method references?
Static method reference (integer:: parseInt)
Instance method reference (string:: length)
Existing object instance method reference (s:: length)
Constructor reference (user:: new)
7. Default method
As mentioned earlier, an interface can have multiple non abstract methods, which is also a new feature of Java 8.
Before Java 8, it was very difficult to extend an existing interface, which would involve the implementation of extension methods by all implementation classes.
Now, when we extend the interface, we only need to provide a default method, which will not affect the implementation class. List and Function have such extensions.
8. Composite lambda
For ease of use, many functional interfaces provide default methods to allow you to perform compound operations.
For example:
Predicate has an and method, which allows two predicates to do and operations and compound them into a more complex expression.
Function has the andThen method. After executing a function, you can take the output as the input and then execute another function (it is often used for Stream processing).
Take the absolute value of the calculated original function through andThen (of course, there is no mathematical significance here, only for the demo of the method):
public static double integrate(Function<Double,Double> pf, double a,double b){ return pf.apply(b) - pf.apply(a); } public static void main(String[] args) { Function<Double,Double> pf = x -> -Math.cos(x); double s = integrate(pf.andThen(Math::abs), 0, Math.PI); System.out.println(s); }
At the same time, we can also write our own functional interfaces and default methods.
The following code shows the case of adding the default method abs() to the functional interface PrimitiveFunction to implement the composite lambda:
public interface PrimitiveFunction{ double getY(double x); default PrimitiveFunction abs() { return x-> Math.abs(getY(x)); } } public static double integrate(PrimitiveFunction pf, double a,double b){ return pf.getY(b) - pf.getY(a); } public static void main(String[] args) { PrimitiveFunction pf = x -> -Math.cos(x); double s = integrate(pf.abs(), 0, Math.PI); System.out.println(s); }
9.void compatibility and variable capture
void compatible
If a Lambda body is a statement expression, it can be compatible with the function descriptor that returns void. This can make it easier for the interface without inverse parameters to use Lambda:
List<String> sList = new ArrayList<>(); Predicate<String> p = c->sList.add(c); Consumer<String> c = sList::add;
The above writing methods are legal, although sList::add returns a boolean, and the function descriptor of Consumer is string - > void
Variable capture
Lambda expressions can use not only body parameters, but also variables defined in their outer scope (similar to anonymous classes). This behavior is called capturing lambda.
In the code block above:
Predicate<String> p = s -> sList.add(s);
sList is the captured outer variable. However, the capture of local variables must be final or equivalent. In the following figure, reassigning the value of sList causes a compilation exception.