Lambda expression _java in QuickStart Java

Source: Internet
Author: User
Tags abstract closure instance method static class stream api uuid java se

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:

Objectref::methodname

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:

PredicateUsed 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)

reducemethod 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.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.