Java SE 8: Lambda Quick Start

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表达式特性
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容