Java Lambda表达式初探

前言

本文受启发于Trisha Gee在JavaOne 2016的主题演讲Refactoring to Java 8

Java 8已经发行两年多,但很多人仍然在使用JDK7。对企业来说,技术上谨慎未必是坏事,但对个人学习而言,不去学习新技术就很可能被技术抛弃。Java 8一个重要的变更是引入Lambda表达式(lambda expression),这听起来似乎很牛,有种我虽然不知道Lambda表达式是什么,但我仍然觉得很厉害的感觉。不要怕,具体到语言层面上Lambda表达式不过是一种新的语法而已,有了它,Java将开启函数式编程的大门。

为什么需要Lambda表达式

不要纠结什么是Lambda表达式、什么是函数式编程。先来看一下Java 8新的语法特性带来的便利之处,相信你会过目不忘的。

在有Lambda表达式之前,要新建一个线程,需要这样写:

new Thread(new Runnable(){
@Override
public void run(){
System.out.println("Thread run()");
}
}).start();

Lambda表达式之后,则可以这样写:

new Thread(
() -> System.out.println("Thread run()")
).start();

正如你所见,之前无用的模板代码不见了!如上所示,Lambda表达式一个常见的用法是取代(某些)匿名内部类,但Lambda表达式的作用不限于此。

Lambda表达式的原理

刚接触Lambda表达式可能觉得它很神奇:不需要声明类或者方法的名字,就可以直接定义函数。这看似是编译器为匿名内部类简写提供的一个小把戏,但事实上并非如此,Lambda表达式实际上是通过invokedynamic指令来实现的。先别管这么多,下面是Lambda表达式几种可能的书写形式,“看起来”并不是很难理解。

Runnable run = () -> System.out.println("Hello World");// 1
ActionListener listener = event -> System.out.println("button clicked");// 2
Runnable multiLine = () -> {// 3
System.out.println("Hello ");
System.out.println("World");
};
BinaryOperator<Long> add = (Long x, Long y) -> x + y;// 4
BinaryOperator<Long> addImplicit = (x, y) -> x + y;// 5

通过上例可以发现:

  • Lambda表达式是有类型的,赋值操作的左边就是类型。Lambda表达式的类型实际上是对应接口的类型
  • Lambda表达式可以包含多行代码,需要用大括号把代码块括起来,就像写函数体那样。
  • 大多数时候,Lambda表达式的参数表可以省略类型,就像代码2和5那样。这得益于javac的类型推导机制,编译器可以根据上下文推导出类型信息。

表面上看起来每个Lambda表达式都是原来匿名内部类的简写形式,该内部类实现了某个函数接口(Functional Interface),但事实比这稍微复杂一些,这里不再展开。所谓函数接口是指内部只有一个接口函数的接口。Java是强类型语言,无论有没有显式指明,每个变量和对象都必须有明确的类型,没有显式指定的时候编译器会尝试确定类型。Lambda表达式的类型就是对应函数接口的类型

Lambda表达式和Stream

Lambda表达式的另一个重要用法,是和Stream一起使用。Stream is a sequence of elements supporting sequential and parallel aggregate operations。Stream就是一组元素的序列,支持对这些元素进行各种操作,而这些操作是通过Lambda表达式指定的。可以把Stream看作Java Collection的一种视图,就像迭代器是容器的一种视图那样(但Stream不会修改容器中的内容)。下面例子展示了Stream的常见用法。

例子1

假设需要从一个字符串列表中选出以数字开头的字符串并输出,Java 7之前需要这样写:

List<String> list = Arrays.asList("1one", "two", "three", "4four");
for(String str : list){
if(Character.isDigit(str.charAt(0))){
System.out.println(str);
}
}

而Java 8就可以这样写:

List<String> list = Arrays.asList("1one", "two", "three", "4four");
list.stream()// 1.得到容器的Steam
.filter(str -> Character.isDigit(str.charAt(0)))// 2.选出以数字开头的字符串
.forEach(str -> System.out.println(str));// 3.输出字符串

上述代码首先1. 调用List.stream()方法得到容器的Stream,2. 然后调用filter()方法过滤出以数字开头的字符串,3. 最后调用forEach()方法输出结果。

使用Stream有两个明显的好处:

  1. 减少了模板代码,只用Lambda表达式指明所需操作,代码语义更加明确、便于阅读。
  2. 将外部迭代改成了Stream的内部迭代,方便了JVM本身对迭代过程做优化(比如可以并行迭代)。

例子2

假设需要从一个字符串列表中,选出所有不以数字开头的字符串,将其转换成大写形式,并把结果放到新的集合当中。Java 8书写的代码如下:

List<String> list = Arrays.asList("1one", "two", "three", "4four");
Set<String> newList =
list.stream()// 1.得到容器的Stream
.filter(str -> !Character.isDigit(str.charAt(0)))// 2.选出不以数字开头的字符串
.map(String::toUpperCase)// 3.转换成大写形式
.collect(Collectors.toSet());// 4.生成结果集

上述代码首先1. 调用List.stream()方法得到容器的Stream,2. 然后调用filter()方法选出不以数字开头的字符串,3. 之后调用map()方法将字符串转换成大写形式,4. 最后调用collect()方法将结果转换成Set。这个例子还向我们展示了方法引用method references,代码中标号3处)以及收集器Collector,代码中标号4处)的用法,这里不再展开说明。

通过这个例子我们看到了Stream链式操作,即多个操作可以连成一串。不用担心这会导致对容器的多次迭代,因为不是每个Stream的操作都会立即执行。Stream的操作分成两类,一类是中间操作(intermediate operations),另一类是结束操作(terminal operation),只有结束操作才会导致真正的代码执行,中间操作只会做一些标记,表示需要对Stream进行某种操作。这意味着可以在Stream上通过关联多种操作,但最终只需要一次迭代。如果你熟悉Spark RDD,对此应该并不陌生。

结语

Java 8引入Lambda表达式,从此打开了函数式编程的大门。如果你之前不了解函数式编程,不必纠结于这个概念。编程过程中简洁明了的书写形式以及强大的Stream API会让你很快熟悉Lambda表达式的。

本文只对Java Lambda表达式的基本介绍,希望能够激发读者对Java函数式编程的兴趣。如果本文能够让你觉得Lambda表达式很好玩,函数式编程很有趣,并产生了进一步学习的欲望,那就再好不过了。文末参考文献中列出了一些有用的资源。

致谢

非常感谢阿里巴巴“西行游学计划”给予我们的支持,感谢阿里中间件团队资助我们远美国赴旧金山参加Java语言的顶级技术会议——2016 JaveOne大会。借此我们有机会跟国际顶尖大牛面对面的交流,并第一时间了解到Java语言的最新动态。同样感谢伴我一起游学的两位队友,有ta们在异国他乡的陪伴和关照,我的西行之旅才更加丰富多彩。这次游学给我们带来的惊喜,将深深留存在我们的记忆当中。

希望更多的同学能够关注并参与到阿里巴巴天池大赛当中,也希望阿里能够继续坚持对青年学生的支持和培养!

参考文献

http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html

https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html

https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html

http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html

http://www.slideshare.net/trishagee/refactoring-to-java-8-devoxx-uk

《Java 8函数式编程 [英]沃伯顿》

https://www.oracle.com/javaone/speakers.html#gee

Java Lambda表达式初探的更多相关文章

  1. Java Lambda表达式入门

    Java Lambda表达式入门 http://blog.csdn.net/renfufei/article/details/24600507 Java 8十个lambda表达式案例 http://w ...

  2. Java Lambda表达式入门[转]

    原文链接: Start Using Java Lambda Expressions http://blog.csdn.net/renfufei/article/details/24600507 下载示 ...

  3. Java Lambda表达式教程与示例

    Lambda表达式是Java 8中引入的一个新特性.一个lambda表达式是一个匿名函数,而且这个函数没有名称且不属于任何类.lambda表达式的概念最初是在LISP编程语言中引入的. Java La ...

  4. Java Lambda表达式forEach无法跳出循环的解决思路

    Java Lambda表达式forEach无法跳出循环的解决思路 如果你使用过forEach方法来遍历集合,你会发现在lambda表达式中的return并不会终止循环,这是由于lambda的底层实现导 ...

  5. 「Flink」使用Java lambda表达式实现Flink WordCount

    本篇我们将使用Java语言来实现Flink的单词统计. 代码开发 环境准备 导入Flink 1.9 pom依赖 <dependencies> <dependency> < ...

  6. java lambda表达式学习笔记

    lambda是函数式编程(FP,functional program),在java8中引入,而C#很早之前就有了.在java中lambda表达式是'->',在C#中是‘=>’. 杜甫说:射 ...

  7. 《Java基础知识》Java Lambda表达式

    接触Lambda表达式的时候,第一感觉就是,这个是啥?我居然看不懂,于是开始寻找资料,必须弄懂它. 先来看一个案例: @FunctionalInterface public interface MyL ...

  8. Java lambda 表达式常用示例

    实体类 package com.lkb.java_lambda.dto; import lombok.Data; /** * @program: java_lambda * @description: ...

  9. Java lambda 表达式详解(JDK 8 新特性)

    什么是 lambda 表达式 lambda 表达式(拉姆达表达式)是 JAVA 8 中提供的一种新的特性,它使 Java 也能进行简单的"函数式编程". lambda 表达式的本质 ...

随机推荐

  1. How and Where Concurrent Asynchronous I/O with ASP.NET Web API 对异步编程分析的非常的好

    How and Where Concurrent Asynchronous I/O with ASP.NET Web API http://www.tugberkugurlu.com/archive/ ...

  2. [Xamarin] 從Xamarin中呼叫 *.jar 的 library - 呼叫篇 (转帖)

    上篇文章我們建立一個很簡單的Library : com.example.blackfactory.UtilFunc 現在我們要在Xamarin 中呼叫囉! 首先我們要先成立一個橋接的專案 JARBri ...

  3. Java设计模式9:代理模式

    代理模式 代理模式的定义很简单:给某一对象提供一个代理对象,并由代理对象控制对原对象的引用. 代理模式的结构 有些情况下,一个客户不想活着不能够直接引用一个对象,可以通过代理对象在客户端和目标对象之间 ...

  4. 用1、2、2、3、4、5这六个数字,用java写一个main函数,打印出所有不同的排列,如:512234、412345等,要求:"4"不能在第三位,"3"与"5"不能相连。

    最近在看算法,看到这个题,觉得挺经典的,收起. 分析: 1 .把问题归结为图结构的遍历问题.实际上6个数字就是六个结点,把六个结点连接成无向连通图,对于每一个结点求这个图形的遍历路径,所有结点的遍历路 ...

  5. 由ASP.NET所谓前台调用后台、后台调用前台想到HTTP——实践篇(二)

    在由ASP.NET所谓前台调用后台.后台调用前台想到HTTP——理论篇中描述了一下ASP.NET新手的三个问题及相关的HTTP协议内容,在由ASP.NET所谓前台调用后台.后台调用前台想到HTTP—— ...

  6. Android Studio2.x版本无法自动关联源码的解决方法

    Android Studio2.x版本无法自动关联源码的解决方法 在学习android开发过程中,对于一个不熟悉的类,阅读源码是一个很好的学习方式,使用andorid studio开发工具的SDK M ...

  7. ehcache2拾遗之cache持久化

    问题描述 应用在使用过程中会需要重启等,但是如果ehcache随着应用一起重启,那么刚重启的时候就会出现大量的miss,需要一定的访问量来重建缓存,如果缓存能够持久化,重启之后可以复用将会有助于缓解重 ...

  8. NSString NSNumber 相互转化

    NSNumberFormatter *numFormatter = [[NSNumberFormatter alloc] init]; NSNumber *firstNum = [NSNumber n ...

  9. 为什么说Java中的随机数都是伪随机数?

    什么是伪随机数?  1.伪随机数是看似随机实质是固定的周期性序列,也就是有规则的随机. 2.只要这个随机数是由确定算法生成的,那就是伪随机,只能通过不断算法优化,使你的随机数更接近随机.   (随机这 ...

  10. [Java面试十一]数据库总结.

    问题及描述: --1.学生表 Student(SID,Sname,Sage,Ssex) --SID 学生编号,Sname 学生姓名,Sage 出生年月,Ssex 学生性别 --2.课程表 Course ...