美文网首页
Java SE 8: Lambda Quick Start

Java SE 8: Lambda Quick Start

作者: 好好学习天天引体向上 | 来源:发表于2017-01-15 10:11 被阅读0次

    Java SE 8: Lambda Quick Start
    施工中

    介绍

    Lambda表达式是Java SE8的重要新特性,提供了一个实现函数接口的简单方法。Lambda表达式改进了Collection库,使得遍历、查询和提取数据更简单。同时,新的并发机制提高了它们多核环形下的表现。

    匿名内部类

    匿名内部类提供了声明代码中只出现一次的类的方法。例如,在表中Swing或JavaFX引用中,需要为键盘或鼠标事件声明很多事件处理类。利用匿名内部类,可以这样写:

    16  JButton testButton = new JButton("Test Button");
    17  testButton.addActionListener(new ActionListener(){
    18      @Override public void actionPerformed(ActionEvent ae){
    19        System.out.println("Click Detected by Anon Class");
    20      }
    21  });
    

    否则,每个事件都要单独声明一个类实现ActionListener接口。通过在需要的地方声明内部类,代码更易读一点。但代码仍然不够优雅,因为声明一个内部类仍需要太多代码。

    函数接口

    ActionListener接口代码如下:

     1 package java.awt.event; 
    2 import java.util.EventListener; 
    3  
    4 public interface ActionListener extends EventListener { 
    5   
    6   public void actionPerformed(ActionEvent e);
    7  
    8 }
    

    ActionListener是一个只有一个方法的接口,在Java SE8中,这种只有一个方法的接口成为函数接口(之前,这类接口被称为Single Abstract Method type SAM)。
    使用内部类实现函数接口在java中普遍适用。Runnable和Comparator也是相同的用法。通过使用Lambda表达式可以改进函数接口的实现。

    Lambda表达式的语法

    Lambda表达式定位于匿名内部类臃肿的代码实现,将原本5行代码压缩为一个表达式。通过水平途径解决垂直问题。
    Lambda表达式由三部分组成

    参数Argument List 箭头 Arrow Token 主体 Body
    (int x, int y) -> x + y

    主体部分可以是一个表达式或一个代码块。
    表达式直接执行并返回。
    代码块,代码被当做方法执行,return语句将结果返回给匿名方法的调用者。在代码块中,break 和 continue关键字非法,但在循环体中仍可以使用。

    Lambda表达式示例

    (int x, int y) -> x + y
    
    () -> 42
    
    (String s) -> { System.out.println(s); } 
    

    第一个表达式输入两个int类型参数x、y,使用表达式方式直接返回x+y。
    第二个表达式无输入,使用表达式方式返回42.
    第三个表达式输入字符串,使用代码块打印字符串,没有返回值。

    Runnable Lambda
     6 public class RunnableTest { 
    7  public static void main(String[] args) { 
    8   
    9    System.out.println("=== RunnableTest ===");
    10  
    11  // Anonymous Runnable
    12  Runnable r1 = new Runnable(){
    13  
    14    @Override
    15    public void run(){
    16      System.out.println("Hello world one!");
    17    }
    18  };
    19  
    20  // Lambda Runnable
    21  Runnable r2 = () -> System.out.println("Hello world two!");
    22  
    23  // Run em!
    24  r1.run();
    25  r2.run();
    26  
    27  }
    28 }
    

    Comparator Lambda

    在Java中,Comparator类用来为集合排序。在下面的例子中,Person实例的队列按照surName属性排序。Person类如下

    9 public class Person {
    10  private String givenName;
    11  private String surName;
    12  private int age;
    13  private Gender gender;
    14  private String eMail;
    15  private String phone;
    16  private String address;
    17 }
    

    使用匿名内部类和Lambda表达式的例子如下:

    10 public class ComparatorTest {
    11 
    12  public static void main(String[] args) {
    13  
    14    List<Person> personList = Person.createShortList();
    15  
    16    // Sort with Inner Class
    17    Collections.sort(personList, new Comparator<Person>(){
    18      public int compare(Person p1, Person p2){
    19        return p1.getSurName().compareTo(p2.getSurName());
    20      }
    21   });
    22  
    23    System.out.println("=== Sorted Asc SurName ===");
    24    for(Person p:personList){
    25      p.printName();
    26    }
    27  
    28    // Use Lambda instead
    29  
    30    // Print Asc
    31    System.out.println("=== Sorted Asc SurName ===");
    32    Collections.sort(personList, (Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName()));
    33 
    34    for(Person p:personList){
    35      p.printName();
    36    }
    37  
    38    // Print Desc
    39    System.out.println("=== Sorted Desc SurName ===");
    40    Collections.sort(personList, (p1, p2) -> p2.getSurName().compareTo(p1.getSurName()));
    41 
    42    for(Person p:personList){
    43      p.printName();
    44    }
    45  
    46  }
    47 }
    

    注意到第一个Lambda表达式声明了参数类型,第二个没有声明。Lambda表达式支持 target typing(泛型目标类型推断),通过上下文推断对象的类型。

    Listener Lambda

    13 public class ListenerTest {
    14  public static void main(String[] args) {
    15  
    16    JButton testButton = new JButton("Test Button");
    17    testButton.addActionListener(new ActionListener(){
    18      @Override public void actionPerformed(ActionEvent ae){
    19        System.out.println("Click Detected by Anon Class");
    20      }
    21    });
    22  
    23    testButton.addActionListener(e -> System.out.println("Click Detected by Lambda Listner"));
    24  
    25    // Swing stuff
    26    JFrame frame = new JFrame("Listener Test");
    27    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    28    frame.add(testButton, BorderLayout.CENTER);
    29    frame.pack();
    30    frame.setVisible(true);
    31  
    32  }
    33 }
    

    利用Lambda表达式改进代码

    Lambda表达式支持了 Don`t repeat yourselt DRY原则,使代码更简洁,更易读。

    普通的查询场景

    代码中常见的场景是遍历数据集合查找符合条件的数据。给定一群人,不同的查询条件,查询出符合条件的人。
    本例中,我们需要找出三类人群:

    • 司机:年龄大于16岁
    • 适龄兵役者:年龄18到25岁
    • 飞行员:年龄23到65岁
      查询结果直接打印在控制台,信息包括姓名、年龄和某个特定信息(电邮地址、电话号码)。
      Person类
    10 public class Person {
    11  private String givenName;
    12  private String surName;
    13  private int age;
    14  private Gender gender;
    15  private String eMail;
    16  private String phone;
    17  private String address;
    18 } 
    

    第一轮

    RoboContactsMethods.java
    1 package com.example.lambda; 
    2  
    3 import java.util.List; 
    4  
    5 /** 
    6  * 
    7  * 
    @author MikeW 
    8  */ 
    9 public class RoboContactMethods { 
    10   
    11  public void callDrivers(List<Person> pl){ 
    12    for(Person p:pl){ 
    13      if (p.getAge() >= 16){ 
    14        roboCall(p); 
    15      } 
    16    } 
    17  } 
    18   
    19  public void emailDraftees(List<Person> pl){ 
    20    for(Person p:pl){ 
    21      if (p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE){
    22        roboEmail(p); 
    23      } 
    24    } 
    25  } 
    26   
    27  public void mailPilots(List<Person> pl){ 
    28    for(Person p:pl){ 
    29      if (p.getAge() >= 23 && p.getAge() <= 65){ 
    30        roboMail(p); 
    31      } 
    32    } 
    33  } 
    34   
    35   
    36  public void roboCall(Person p){ 
    37    System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone()); 
    38  } 
    39   
    40  public void roboEmail(Person p){ 
    41    System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail()); 
    42  } 
    43   
    44  public void roboMail(Person p){ 
    45    System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress()); 
    46  } 
    47  
    48 }
    

    这一实现的缺点:

    • 没有遵守DRY原则
    • 重复使用循环机制
    • 每个查询条件对应一个方法
    • 代码无法扩展,如果查询条件发生变化,需要修改代码。

    重构查询方法

    通过匿名内部类实现。声明MyTest接口,只有一个条件验证函数,返回boolean值。查询条件在方法调用时传递。接口定义如下:

    6 public interface MyTest<T> {
    7  public boolean test(T t);
    8 }
    

    更新后的实现如下:

    RoboContactsAnon.java
     9 public class RoboContactAnon {
    10 
    11  public void phoneContacts(List<Person> pl, MyTest<Person> aTest){
    12    for(Person p:pl){
    13      if (aTest.test(p)){
    14        roboCall(p);
    15      }
    16    }
    17  }
    18 
    19  public void emailContacts(List<Person> pl, MyTest<Person> aTest){
    20    for(Person p:pl){
    21      if (aTest.test(p)){
    22        roboEmail(p);
    23      }
    24    }
    25  }
    26 
    27  public void mailContacts(List<Person> pl, MyTest<Person> aTest){
    28    for(Person p:pl){
    29       if (aTest.test(p)){
    30         roboMail(p);
    31      }
    32    }
    33  } 
    34  
    35  public void roboCall(Person p){
    36    System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
    37  }
    38  
    39  public void roboEmail(Person p){
    40    System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
    41  }
    42  
    43  public void roboMail(Person p){
    44    System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
    45  }
    46  
    47 }
    

    代码仍然臃肿,可读性不高,每个查询条件都需要单独实现。

    Lambda表达式

    java.util.function
    在Java SE8中提供了JUF包有多个标准函数接口,在本例中,Predicate接口满足我们的需要。

    3 public interface Predicate<T> {
    4  public boolean test(T t);
    5 }
    

    本例最终形态:

    RoboContactsLambda.java
    1 package com.example.lambda; 
    2  
    3 import java.util.List; 
    4 import java.util.function.Predicate; 
    5  
    6 /**
    7  * 
    8  * @author MikeW 
    9  */ 
    10 public class RoboContactLambda { 
    11  public void phoneContacts(List<Person> pl, Predicate<Person> pred){ 
    12    for(Person p:pl){ 
    13      if (pred.test(p)){ 
    14        roboCall(p); 
    15      } 
    16    } 
    17  } 
    18  
    19  public void emailContacts(List<Person> pl, Predicate<Person> pred){ 
    20    for(Person p:pl){ 
    21      if (pred.test(p)){ 
    22        roboEmail(p); 
    23      } 
    24    } 
    25  } 
    26  
    27  public void mailContacts(List<Person> pl, Predicate<Person> pred){ 
    28    for(Person p:pl){ 
    29      if (pred.test(p)){ 
    30        roboMail(p); 
    31      } 
    32    } 
    33  } 
    34   
    35  public void roboCall(Person p){ 
    36    System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone()); 
    37  } 
    38   
    39  public void roboEmail(Person p){ 
    40    System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail()); 
    41  } 
    42   
    43  public void roboMail(Person p){ 
    44    System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress()); 
    45  } 
    46  
    47 }
    
    RoboCallTest04.java
    1 package com.example.lambda; 
    2  
    3 import java.util.List; 
    4 import java.util.function.Predicate; 
    5  
    6 /** 
    7  * 
    8  * @author MikeW 
    9  */ 
    10 public class RoboCallTest04 { 
    11   
    12  public static void main(String[] args){  
    13  
    14    List<Person> pl = Person.createShortList(); 
    15    RoboContactLambda robo = new RoboContactLambda(); 
    16   
    17    // Predicates 
    18    Predicate<Person> allDrivers = p -> p.getAge() >= 16; 
    19    Predicate<Person> allDraftees = p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE; 
    20    Predicate<Person> allPilots = p -> p.getAge() >= 23 && p.getAge() <= 65; 
    21   
    22    System.out.println("\n==== Test 04 ===="); 
    23    System.out.println("\n=== Calling all Drivers ==="); 
    24    robo.phoneContacts(pl, allDrivers); 
    25   
    26    System.out.println("\n=== Emailing all Draftees ==="); 
    27    robo.emailContacts(pl, allDraftees); 
    28   
    29    System.out.println("\n=== Mail all Pilots ==="); 
    30    robo.mailContacts(pl, allPilots); 
    31   
    32    // Mix and match becomes easy 
    33    System.out.println("\n=== Mail all Draftees ==="); 
    34    robo.mailContacts(pl, allDraftees);  
    35   
    36    System.out.println("\n=== Call all Pilots ==="); 
    37    robo.phoneContacts(pl, allPilots);  
    38   
    39    } 
    40 }
    

    代码紧凑易读,同时没有重复代码问题。

    JUF包

    • Predicate: 传入对象,返回boolean值
    • Consumer: 传入对象,没有返回值
    • Function: 传入类型T对象,返回类型U对象
    • Supplier: 无传入值,返回T类型对象
    • UnaryOperator: 一元操作,传入T类型,返回T类型
    • BinaryOperator: 二元操作,传入T类型,返回T类型

    Lambda表达式与Collections

    循环

    首先是所有collection类支持的forEach方法。下面的例子展示打印Person队列的各种方法。

    Test01ForEach.java
    11 public class Test01ForEach {
    12  
    13  public static void main(String[] args) {
    14  
    15    List<Person> pl = Person.createShortList();
    16  
    17    System.out.println("\n=== Western Phone List ===");
    18    pl.forEach( p -> p.printWesternName() );
    19  
    20    System.out.println("\n=== Eastern Phone List ===");
    21    pl.forEach(Person::printEasternName);
    22  
    23    System.out.println("\n=== Custom Phone List ===");
    24    pl.forEach(p -> { System.out.println(p.printCustom(r -> "Name: " + r.getGivenName() + " EMail: " + r.getEmail())); });
    25  
    26  }
    27 
    28 }
    

    18行使用Lambda表达式打印名字,21行使用方法引用调用静态方法,24行注意Lambda表达式嵌套式的参数名。

    链式过滤

    filter方法接收Predicate实例,过滤集合,返回过滤后的结果。

    Test02Filter.java
     9 public class Test02Filter {
    10  
    11  public static void main(String[] args) {
    12 
    13    List<Person> pl = Person.createShortList();
    14  
    15    SearchCriteria search = SearchCriteria.getInstance();
    16  
    17    System.out.println("\n=== Western Pilot Phone List ===");
    18 
    19    pl.stream().filter(search.getCriteria("allPilots"))
    20      .forEach(Person::printWesternName);
    21  
    22  
    23    System.out.println("\n=== Eastern Draftee Phone List ===");
    24 
    25    pl.stream().filter(search.getCriteria("allDraftees"))
    26      .forEach(Person::printEasternName);
    27  
    28  }
    29 }
    

    懒加载

    这里的lazy和eager没有想到合适的翻译,保留原文

    Getting Lazy

    通过向Collection包种加入新的枚举方式,java开发人员可以做更多的代码优化。
    Laziness:指系统仅在必要时处理必须处理的对象。在上面的例子中,forEach是lazy模式的,因为这次遍历仅仅涉及两个Person对象,后续操作只发生在过滤后的对象上,代码的效率提高了。
    Eagerness:代码遍历整个对象队列执行操作。

    通过将forEach加入collection包,代码可以在合适的地方进行Lazy优化,在其他需要eager模式(如求和或求平均值)的地方仍使用eager模式。这一方式使得代码更高效,更有弹性。

    流方法

    stream方法以Collection对象为输入,以StreamInterface对象作为输出。Stream就像Iterator,只能遍历一次,不能修改其中的对象。Stream支持单线程和并行执行。

    获取结果集

    Stream操作的结果可以通过创建新的collection对象保存。下面的例子展示了如何将集合遍历的结果存入新的集合对象中。

    Test03toList.java
    10 public class Test03toList {
    11  
    12  public static void main(String[] args) {
    13  
    14    List<Person> pl = Person.createShortList();
    15  
    16    SearchCriteria search = SearchCriteria.getInstance();
    17  
    18    // Make a new list after filtering.
    19    List<Person> pilotList = pl
    20      .stream()
    21      .filter(search.getCriteria("allPilots"))
    22      .collect(Collectors.toList());
    23  
    24    System.out.println("\n=== Western Pilot Phone List ===");
    25    pilotList.forEach(Person::printWesternName);
    26 
    27  }
    28 
    29 }
    

    集合计算

    下面的例子展示了如何利用map方法获取对象的某个值,然后执行计算操作。注意Stream是并行执行的,返回值也略有不同。

    Test04Map.java
    10 public class Test04Map {
    11 
    12  public static void main(String[] args) {
    13    List<Person> pl = Person.createShortList();
    14  
    15    SearchCriteria search = SearchCriteria.getInstance();
    16  
    17    // Calc average age of pilots old style
    18    System.out.println("== Calc Old Style ==");
    19    int sum = 0;
    20    int count = 0;
    21  
    22    for (Person p:pl){
    23      if (p.getAge() >= 23 && p.getAge() <= 65 ){
    24        sum = sum + p.getAge();
    25        count++;
    26      }
    27    }
    28  
    29    long average = sum / count;
    30    System.out.println("Total Ages: " + sum);
    31    System.out.println("Average Age: " + average);
    32  
    33  
    34    // Get sum of ages
    35    System.out.println("\n== Calc New Style ==");
    36    long totalAge = pl
    37      .stream()
    38      .filter(search.getCriteria("allPilots"))
    39      .mapToInt(p -> p.getAge())
    40      .sum();
    41 
    42    // Get average of ages
    43    OptionalDouble averageAge = pl
    44      .parallelStream()
    45      .filter(search.getCriteria("allPilots"))
    46      .mapToDouble(p -> p.getAge())
    47      .average();
    48 
    49    System.out.println("Total Ages: " + totalAge);
    50    System.out.println("Average Age: " + averageAge.getAsDouble()); 
    51  
    52    }
    53  
    54 }
    

    总结

    • 匿名内部类
    • Lambda表达式替代匿名内部类
    • Lambda表达式的语法
    • Function包的Predicate借口实现集合过滤操作
    • Collections包中增加的Lambda表达式特性

    相关文章

      网友评论

          本文标题:Java SE 8: Lambda Quick Start

          本文链接:https://www.haomeiwen.com/subject/ujhybttx.html