欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java SE 8: Lambda Quick Start

程序员文章站 2022-06-09 21:12:45
...

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表达式特性