Introduction to Lambda
Lambda expressions are an important new feature in Java SE 8. Lambda expressions allow you to use expressions instead of functional interfaces. A lambda expression, like a method, provides a normal list of arguments and a body that uses those arguments (the bodies, which can be an expression or a block of code).
Lambda expressions also enhance the collection library. Java SE 8 adds 2 packages for bulk operations on collection data: java.util.function
packages and java.util.stream
packages. Flow (stream) is like an iterator (iterator), but many additional features are attached. In general, lambda expressions and stream are the biggest changes since the Java language adds generics (generics) and annotations (annotation).
A lambda expression is essentially an anonymous method, and it is implemented by an invokedynamic
instruction to generate an anonymous class. It provides a simpler syntax and writing style that allows you to substitute a functional interface with an expression. In the view of some, a lambda is a way to make your code more concise and completely unused-which is certainly fine, but it's important that the lambda brings closure to Java. Thanks to the support of the LAMDBA to the collection, the performance of the set traversal is greatly enhanced by the lambda in the multi-core processor condition, and we can handle the collection in the form of data flow-which is very attractive.
Lambda syntax
The syntax of a lambda is extremely simple, similar to the following structure:
(parameters)-> expression
Or
(parameters)-> {statements;}
A lambda expression is made up of three parts:
1, paramaters: A similar method in the formal parameter list, where the parameter is a function of the interface parameters. The parameter types here can be explicitly declared or implicitly inferred by the JVM. In addition, parentheses can be omitted when there is only one inferred type.
2.: may be understood as "used" means
3, method body: can be an expression can also code block, is a function of the interface method implementation. A code block can return a value or nothing back, and the block of code here is equivalent to the method body of the approach. If it is an expression, it can also return a value or nothing back.
We illustrate this by following several examples:
Example 1: You do not need to accept parameters and return directly to
()->10
//Example 2: Accept arguments of two int types and return the sum
(int x,int y) of the two arguments->x+y;
Example 2: Accepts x,y two parameters, the type of which is inferred by the JVM from the context and returns two parameters of the and
(x,y)->x+y;
Example 3: Accepts a string and prints the string to the control, not back to the result
(string name)->system.out.println (name);
Example 4: Accepts the parameter name of an inferred type and prints the string to the console
name->system.out.println (name);
Example 5: Accepts two string type parameters and outputs separately, not back
(string name,string Sex)->{system.out.println (name); SYSTEM.OUT.PRINTLN (Sex)}
//Example 6: Accept an argument x and return twice times that argument
x->2*x
Where is the lambda used
In [Functional interface][1] We know that the target type of a lambda expression is a functional interface--each lambda can match a given type with a specific functional interface. So a lambda expression can be applied to any place that matches its target type, the lambda expression must describe the same parameter type as the abstract function of the functional interface, and its return type must be compatible with the return type of the abstract function, and the exception that he can throw is limited to the description scope of the function.
Next, let's look at a custom functional interface example:
@FunctionalInterface
interface converter<f, t>{
T convert (F from);
}
The interface is first used in the traditional way:
Converter<string,integer> converter=new converter<string, integer> () {
@Override public
Integer Convert (String from) {return
integer.valueof (from);
}
;
Integer result = Converter.convert ("a");
SYSTEM.OUT.PRINTLN (result);
Obviously, there's no problem, then it's the time for the Lambda to play, and the converter interface is implemented with a lambda:
converter<string,integer> converter= (param)-> integer.valueof (param);
Integer result = Converter.convert ("a");
SYSTEM.OUT.PRINTLN (result);
In the last example, I think you have a simple understanding of the use of lambda, and below, we are using a common runnable demo:
In the past we might have written this code:
New Thread (New Runnable () {
@Override public
void Run () {
System.out.println ("Hello Lambda");
}
). Start ();
In some cases, a large number of anonymous classes can make the code look cluttered. You can now use a lambda to make it simple:
New Thread (()-> System.out.println ("Hello Lambda"). Start ();
Method reference
A method reference is a simplified formulation of a lambda expression. The method referred to is actually the implementation of the method body of the lambda expression, and its syntax structure is:
The left can be a class or instance name, with the method reference symbol "::" In the middle, and the appropriate method name on the right.
Method references are grouped into three categories:
1. Static method Reference
In some cases, we might write this code:
public class Referencetest {public
static void Main (string[] args) {
converter<string,integer> Converter =new converter<string, integer> () {
@Override public
Integer convert (String from) {
return Referencetest.string2int (from);
}
;
Converter.convert ("the");
}
@FunctionalInterface
Interface converter<f,t>{
T convert (F from);
}
static int String2int (String from) {return
integer.valueof (from);
}
}
This time, if you use static references to make the code more concise:
converter<string, integer> Converter = referencetest::string2int;
Converter.convert ("120");
2. Instance method reference
We might also write this code:
public class Referencetest {public
static void Main (string[] args) {
converter<string, integer> Converter = new converter<string, integer> () {
@Override public
Integer convert (String from) {return
new Helper (). String2int (from);
}
;
Converter.convert ("the");
}
@FunctionalInterface
Interface Converter<f, t> {
T convert (F from);
}
Static class Helper {public
int string2int (String from) {return
integer.valueof (from);
}
}}
It is also simpler to use instance method references:
Helper helper = new Helper ();
converter<string, integer> Converter = helper::string2int;
Converter.convert ("120");
3. Construct method Reference
Now let's demonstrate the reference to the constructor method. First we define a parent class animal:
Class animal{
private String name;
private int age;
Public Animal (String name, int age) {
this.name = name;
This.age = age;
}
public void behavior () {
}
}
Next, we're defining two animal subclasses:Dog、Bird
public class Bird extends Animal {public
Bird (String name, int age) {
super (name, age);
}
@Override public
Void behavior () {
System.out.println ("Fly");
}
Class Dog extends Animal {public
Dog (String name, int age) {
super (name, age);
}
@Override public
Void behavior () {
System.out.println ("Run");
}
Then we define the factory interface:
Interface Factory<t extends animal> {
T Create (String name, int age);
The next step is to create the objects of the dog class and the bird class in a traditional way:
Factory factory=new Factory () {
@Override public
Animal Create (String name, int age) {return
new Dog (Name,ag e);
}
;
Factory.create ("Alias", 3);
Factory=new Factory () {
@Override public
Animal Create (String name, int age) {return
new Bird (name,age); c11/>}
};
Factory.create ("Smook", 2);
Just to create two objects to write 10 supposedly code, now we use the constructor reference to try:
Factory<animal> dogfactory =dog::new;
Animal dog = Dogfactory.create ("Alias", 4);
factory<bird> birdfactory = bird::new;
Bird Bird = birdfactory.create ("Smook", 3);
So the code looks neat. In Dog::new
this way, when the object is worn, the Factory.create
function signature chooses the corresponding function.
Domain of the lambda and access restrictions
The domain is the scope, and the parameters in the parameter list in the lambda expression are valid within the scope of the lambda expression (field). Within the action lambda expression, you can access external variables: local, class, and static variables, but with varying degrees of operation restrictions.
Accessing local variables
Local variables outside the lambda expression are implicitly compiled by the JVM into the final type, so they can only be accessed externally and cannot be modified.
public class Referencetest {public
static void Main (string[] args) {
int n = 3;
Calculate Calculate = param-> {
//n=10; Compile error return
n + param;
};
Calculate.calculate (ten);
}
@FunctionalInterface
interface Calculate {
int Calculate (int value);
}
}
Accessing static variables and member variables
Inside a lambda expression, you can read and write to static variables and member variables.
public class Referencetest {public
int count = 1;
public static int num = 2;
public void Test () {
Calculate Calculate = param-> {
num = 10;//Modify static variable
count = 3;//Modify member variable return
n + P Aram;
};
Calculate.calculate (ten);
}
public static void Main (string[] args) {
}
@FunctionalInterface
interface Calculate {
int Calculate (int value);
}
}
A lambda cannot access the default method of a function interface
JAVA8 enhances the interface, which includes the default method that interfaces can add to the definition of a default keyword, where we need to note that the access default method is not supported within lambda expressions.
Lambda practice
In the [Functional Interface][2] section, we mentioned that there are java.util.function
many functional interfaces built into the package, and now we will explain the common functional interfaces.
Predicate interface
Enter a parameter and return a Boolean
value with a number of default methods for logical judgments built into it:
@Test public
void Predicatetest () {
predicate<string> predicate = (s)-> s.length () > 0;
Boolean test = predicate.test ("test");
System.out.println ("string length greater than 0:" + test);
Test = Predicate.test ("");
System.out.println ("string length greater than 0:" + test);
Test = Predicate.negate (). Test ("");
System.out.println ("string length less than 0:" + test);
predicate<object> pre = Objects::nonnull;
Object OB = null;
Test = pre.test (OB);
System.out.println ("object is not NULL:" + test);
OB = new Object ();
Test = pre.test (OB);
System.out.println ("object is not NULL:" + test);
}
Function interface
Receives an argument that returns a single result, and the default method ( andThen
) can string together multiple functions to form a composite Funtion
(with input, output) results,
@Test public
void FunctionTest () {
function<string, integer> tointeger = integer::valueof;
The execution result of the Tointeger as input to the second backtostring
function<string, string> backtostring = Tointeger.andthen (String:: valueof);
String result = backtostring.apply ("1234");
SYSTEM.OUT.PRINTLN (result);
Function<integer, integer> add = (i)-> {
System.out.println ("Frist input:" + i);
return I * 2;
};
Function<integer, integer> zero = Add.andthen ((i)-> {
System.out.println ("second input:" + i);
return i * 0;
});
Integer res = zero.apply (8);
System.out.println (res);
Supplier interface
Returns the result of a given type, with the Function
difference being that Supplier
there is no need to accept parameters (supplier, have output without input)
@Test public
void Suppliertest () {
supplier<string> Supplier = ()-> "Special type value";
String s = supplier.get ();
System.out.println (s);
}
Consumer interface
Represents an action that needs to be performed on a single input parameter. And Function
the difference is, Consumer
no return value (consumer, have input, no output)
@Test public
void Consumertest () {
consumer<integer> add5 = (p)-> {
System.out.println ("Old Value: "+ P");
p = p + 5;
SYSTEM.OUT.PRINTLN ("New value:" + P);
};
Add5.accept (ten);
}
The use of the above four interfaces represents java.util.function
four types in the package, and after understanding the four functional interfaces, the other interfaces are easy to understand, so let's do a quick summary:
Predicate
Used in logical judgments, where there is input and output, where there is Function
Supplier
no input, there is output, and where Consumer
there is input and no output. You can learn the use of the scene by the meaning of its name.
Stream
Lambda has a closure for java8, which is particularly important in set operations: JAVA8 supports functional operations on the stream of collection objects, and the stream API is integrated into the collection API, allowing bulk operations on collection objects.
Now let's get to know the stream.
Stream represents a stream of data that has no data structure and does not itself store elements, and its operation does not change the source stream, but rather generates a new stream. As an interface to manipulate data, it provides filtering, sequencing, mapping, specification, and many other methods of operation, These methods are divided into two classes according to the return type: The method that returns the stream type is called the intermediate method (intermediate operation) and the rest is the end method (the end operation). The End method returns a value of one type, and the middle method returns a new stream. The call to the intermediate method is usually chain-type, this process forms a pipe that, when the end method is invoked, causes the value to be consumed immediately from the pipeline, and here we should remember that the stream operates as "delayed", which is what we often call "lazy operation", which helps to reduce resource consumption and improve performance. All intermediate operations (except sorted) are run in deferred mode.
Stream not only provides a powerful data manipulation capability, but more importantly the stream supports both serial and parallel, which makes the stream have better performance on multi-core processors.
The use of the stream has a fixed pattern:
1. Create stream
2, through the middle operation, the original stream to "change" and generate a new stream
3, the use of the completion of the operation to produce the final results
Is
Create--> Change--> end
The creation of the stream
For collections, it can be created by calling the collection or by invoking the stream()
parallelStream()
two methods, which are also implemented in the collection interface. For arrays, it can be created by the static method of the stream, and of(T … values)
arrays also provides support for the stream.
In addition to creating the stream based on a collection or array, you can create an Steam.empty()
infinite stream by creating an empty stream or by using the stream generate()
.
We take serial stream as an example to illustrate several common intermediate and end methods of stream. First create a list collection:
List<string> lists=new arraylist<string > ();
Lists.add ("A1");
Lists.add ("A2");
Lists.add ("B1");
Lists.add ("B2");
Lists.add ("B3");
Lists.add ("O1");
Intermediate method
Filters (Filter)
In conjunction with the predicate interface, filter all elements of the filter convection object, which is an intermediary operation, which means that you can perform other operations on the basis of the results returned by the operation.
public static void Streamfiltertest () {
lists.stream (). Filter ((S-> s.startswith ("a")). ForEach (System.out:: println);
Equivalent to the above operation
predicate<string> predicate = (s)-> S.startswith ("a");
Lists.stream (). filter (predicate). ForEach (System.out::p rintln);
Continuous filtration
predicate<string> predicate1 = (s-> s.endswith ("1"));
Lists.stream (). filter (predicate). Filter (predicate1). ForEach (System.out::p rintln);
Sort (Sorted)
Combined with the comparator interface, the operation returns a view of the sorted stream, and the order of the original stream does not change. You specify collations by comparator, which are sorted in natural order by default.
public static void Streamsortedtest () {
System.out.println ("Default Comparator");
Lists.stream (). Sorted (). Filter ((S-> s.startswith ("a")). ForEach (System.out::p rintln);
SYSTEM.OUT.PRINTLN ("Custom Comparator");
Lists.stream (). Sorted (P1, p2)-> P2.compareto (p1)). Filter ((S-> s.startswith ("a")). ForEach (System.out:: println);
}
Map (map)
With an Function
interface, this operation maps each element in a stream object to another element and implements the conversion of the element type.
public static void Streammaptest () {
lists.stream (). Map (String::touppercase). Sorted ((A, B)-> B.compareto (a)). ForEach (System.out::p rintln);
SYSTEM.OUT.PRINTLN ("Custom mapping Rules");
Function<string, string> function = (p)-> {return
p + ". txt";
};
Lists.stream (). Map (string::touppercase). Map (function). Sorted ((A, B)-> B.compareto (a)). ForEach (System.out:: println);
}
The above three common operations are briefly described, and these three operations greatly simplify the processing of the collection. Next, you'll introduce several methods of completion:
End method
After the transform process, you need to get the result, that is, to complete the operation. Let's look at the related actions:
Matching (match)
Used to determine predicate
whether a stream object matches or not, and ultimately return Boolean
the type result, for example:
The public static void Streammatchtest () {
///Stream object returns True
boolean Anystartwitha = Lists.stream () As long as there is an element match. AnyMatch ((S-> s.startswith ("a"));
System.out.println (Anystartwitha);
Returns True
boolean Allstartwitha
= Lists.stream (). Allmatch ((S-> s.startswith ("a"
)) if each element in the Stream object matches System.out.println (Allstartwitha);
}
Collection (Collect)
After the transformation, we collect the elements of the transformed stream, such as saving the elements to the collection, and then we can use the Collect method provided by the stream, for example:
public static void Streamcollecttest () {
list<string> List = Lists.stream (). Filter ((p)-> P.startswith ("a" ). Sorted (). Collect (Collectors.tolist ());
SYSTEM.OUT.PRINTLN (list);
Count (Count)
SQL-like count, which is used to count the total number of elements in the stream, for example:
public static void Streamcounttest () {
Long Count = Lists.stream (). Filter ((S-> s.startswith ("a")). Count ();
System.out.println (count);
}
Statute (Reduce)
reduce
method allows us to compute elements in our own way or to associate elements of a stream with some regularity, such as:
public static void Streamreducetest () {
optional<string> Optional = Lists.stream (). sorted (). Reduce ((S1, S2) -> {
System.out.println (S1 + "|" + s2);
return S1 + "|" + s2;}
);
The results of the implementation are as follows:
A1|A2
a1|a2|b1
a1|a2|b1|b2
a1|a2|b1|b2|b3
a1|a2|b1|b2|b3|o1
Parallel stream VS Serial stream
We have already introduced the common intermediate operation and the end operation. Of course all of the examples are based on serial stream. Next, we'll introduce the key play-parallel stream (parallel stream). Parallel stream based on the Fork-join parallel decomposition framework, the large data set into a number of small data into a combination of different threads to deal with, so in the case of multi-core processing, performance will be greatly improved. This is consistent with the design concept of MapReduce: Large task small, small task redistribution to different machine execution. But the small task here is to give a different processor.
By parallelStream()
creating a parallel stream. To verify that the parallel stream really improves performance, we execute the following test code:
First, create a larger set:
list<string> biglists = new arraylist<> ();
for (int i = 0; i < 10000000 i++) {
uuid uuid = Uuid.randomuuid ();
Biglists.add (Uuid.tostring ());
}
Test the time it takes to sort by serial stream:
private static void Notparallelstreamsortedtest (List<string> biglists) {
Long starttime = System.nanotime ();
Long Count = Biglists.stream (). Sorted (). Count ();
Long endtime = System.nanotime ();
Long Millis = TimeUnit.NANOSECONDS.toMillis (endtime-starttime);
System.out.println (System.out.printf ("Serial Sort:%d ms", Millis));
}
Test the time spent in parallel streaming:
private static void Parallelstreamsortedtest (List<string> biglists) {
Long starttime = System.nanotime ();
Long Count = Biglists.parallelstream (). Sorted (). Count ();
Long endtime = System.nanotime ();
Long Millis = TimeUnit.NANOSECONDS.toMillis (endtime-starttime);
System.out.println (System.out.printf ("parallel sort:%d ms", Millis));
}
The results are as follows:
Serial Sort: 13336 ms
Parallel Sort: 6755 ms
See here, we do find that the performance improvement is about 50%, you may also want to use all parallel Stream
the time soon? In fact, if you are still a single core processor, and the data volume is not very large, the serial flow is still a good choice. You will also find that in some cases, the performance of the serial flow is better, as for the specific use, you need to test the actual scene before you decide.
Lazy operation
Above we talked about the stream running as late as possible, by creating an infinite stream to illustrate:
First, a sequence of natural numbers is passed through the stream generate
method, which is then transformed by a map
stream:
Increment sequence
class Natureseq implements supplier<long> {
Long value = 0;
@Override public
Long get () {
value++;
return value;
}
}
public void Streamcreatetest () {
stream<long> Stream = stream.generate (New Natureseq ());
System.out.println ("Number of elements:" +stream.map (param)-> {return
param;
}). Limit (1000). Count ());
The results of the execution are:
Number of elements: 1000
We find that it is possible to do any intermediate operations (e.g. filter,map
, etc, but not) on this infinite stream at first sorted
, that is, the process of intermediate operation of the stream and the existence of a new stream does not take effect immediately (otherwise, in map
this case The operation will run forever and be blocked, and the stream does not begin to compute until the end method is encountered. By limit()
Way of this, the infinite stream is converted into a poor stream.
Summarize
This is the Java Lambda Quick start to explain all the content, after reading this article is not the Java lambda have a deeper understanding, I hope this article for you to learn Java lambda can help.