Java SE 8: Lambda Quick Start

2017-01-15  本文已影响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原则,使代码更简洁,更易读。

普通的查询场景

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

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 }

这一实现的缺点:

重构查询方法

通过匿名内部类实现。声明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包

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 }

总结

上一篇下一篇

猜你喜欢

热点阅读