感谢同事【天锦】的投稿。投稿请联系 tengfei@ifeve.com
本文主要记录自己学习Java8的历程,方便大家一起探讨和自己的备忘。因为本人也是刚刚开始学习Java8,所以文中肯定有错误和理解偏差的地 方,希望大家帮忙指出,我会持续修改和优化。本文是该系列的第一篇,主要介绍Java8对屌丝码农最有吸引力的一个特性—lambda表达式。
java8的安装
工欲善其器必先利其器,首先安装JDK8。过程省略,大家应该都可以自己搞定。但是有一点这里强调一下(Windows系统):目前我们工作的版本 一般是java 6或者java 7,所以很多人安装java8基本都是学习为主。这样就在自己的机器上会存在多版本的JDK。而且大家一般是希望在命令行中执行java命令是基于老版本 的jdk。但是在安装完jdk8并且没有设置path的情况下,你如果在命令行中输入:java -version,屏幕上会显示是jdk 8。这是因为jdk8安装的时候,会默认在C:/Windows/System32中增加java.exe,这个调用的优先级比path设置要高。所以即 使path里指定是老版本的jdk,但是执行java命令显示的依然是新版本的jdk。这里我们要做的就是删除C:/Windows/System32中 的java.exe文件(不要手抖!)。
Lambda初体验
下面进入本文的正题–lambda表达式。首先我们看一下什么是lambda表达式。以下是维基百科上对于”Lambda expression”的解释:
a function (or a subroutine) defined, and possibly called, without being bound to an identifier。
简单点说就是:一个不用被绑定到一个标识符上,并且可能被调用的函数。这个解释还不够通俗,lambda表达式可以这样定义(不精确,自己的理 解):一段带有输入参数的可执行语句块。这样就比较好理解了吧?一例胜千言。有读者反馈:不理解Stream的含义,所以这里先提供一个没用stream 的lambda表达式的例子。
2 |
List<String> names = ...; |
3 |
Collections.sort(names, (o1, o2) -> o1.compareTo(o2)); |
2 |
List<String> names = ...; |
3 |
Collections.sort(names, new Comparator<String>() { |
5 |
public int compare(String o1, String o2) { |
6 |
return o1.compareTo(o2); |
上面两段代码分别是:使用lambda表达式来排序和使用匿名内部类来排序。这个例子可以很明显的看出lambda表达式简化代码的效果。接下来展示lambda表达式和其好基友Stream的配合。
1 |
List<String> names = new ArrayList<>(); |
4 |
List<String> lowercaseNames = names.stream().map((String name) -> {return name.toLowerCase();}).collect(Collectors.toList()); |
这段代码就是对一个字符串的列表,把其中包含的每个字符串都转换成全小写的字符串(熟悉Groovy和Scala的同学肯定会感觉很亲切)。注意代 码第四行的map方法调用,这里map方法就是接受了一个lambda表达式(其实是一个java.util.function.Function的实 例,后面会介绍)。
为什么需要Lambda表达式呢?在尝试回答这个问题之前,我们先看看在Java8之前,如果我们想做上面代码的操作应该怎么办。
先看看普通青年的代码:
1 |
List<String> names = new ArrayList<>(); |
4 |
List<String> lowercaseNames = new ArrayList<>(); |
5 |
for (String name : names) { |
6 |
lowercaseNames.add(name.toLowerCase()); |
接下来看看文艺青年的代码(借助Guava):
1 |
List<String> names = new ArrayList<>(); |
4 |
List<String> lowercaseNames = FluentIterable.from(names).transform(new Function<String, String>() { |
6 |
public String apply(String name) { |
7 |
return name.toLowerCase(); |
在此,我们不再讨论普通青年和文艺青年的代码风格孰优孰劣(有兴趣的可以去google搜索“命令式编程vs声明式编程”)。本人更加喜欢声明式的 编程风格,所以偏好文艺青年的写法。但是在文艺青年代码初看起来看起来干扰信息有点多,Function匿名类的构造语法稍稍有点冗长。所以Java8的 lambda表达式给我们提供了创建SAM(Single Abstract Method)接口更加简单的语法糖。
Lambda语法详解
我们在此抽象一下lambda表达式的一般语法:
1 |
(Type1 param1, Type2 param2, ..., TypeN paramN) -> { |
从lambda表达式的一般语法可以看出来,还是挺符合上面给出的非精确版本的定义–“一段带有输入参数的可执行语句块”。
上面的lambda表达式语法可以认为是最全的版本,写起来还是稍稍有些繁琐。别着急,下面陆续介绍一下lambda表达式的各种简化版:
1. 参数类型省略–绝大多数情况,编译器都可以从上下文环境中推断出lambda表达式的参数类型。这样lambda表达式就变成了:
1 |
(param1,param2, ..., paramN) -> { |
所以我们最开始的例子就变成了(省略了List的创建):
1 |
List<String> lowercaseNames = names.stream().map((name) -> {return name.toLowerCase();}).collect(Collectors.toList()); |
2. 当lambda表达式的参数个数只有一个,可以省略小括号。lambda表达式简写为:
所以最开始的例子再次简化为:
1 |
List<String> lowercaseNames = names.stream().map(name -> {return name.toLowerCase();}).collect(Collectors.toList()); |
3. 当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号。lambda表达式简化为:
所以最开始的例子再次简化为:
1 |
List<String> lowercaseNames = names.stream().map(name -> name.toLowerCase()).collect(Collectors.toList()); |
4. 使用Method Reference(具体语法后面介绍)
1 |
//注意,这段代码在Idea 13.0.2中显示有错误,但是可以正常运行 |
2 |
List<String> lowercaseNames = names.stream().map(String::toLowerCase).collect(Collectors.toList()); |
Lambda表达式眼中的外部世界
我们前面所有的介绍,感觉上lambda表达式像一个闭关锁国的家伙,可以访问给它传递的参数,也能自己内部定义变量。但是却从来没看到其访问它外
部的变量。是不是lambda表达式不能访问其外部变量?我们可以这样想:lambda表达式其实是快速创建SAM接口的语法糖,原先的SAM接口都可以
访问接口外部变量,lambda表达式肯定也是可以(不但可以,在java8中还做了一个小小的升级,后面会介绍)。
1 |
String[] array = {"a", "b", "c"}; |
2 |
for(Integer i : Lists.newArrayList(1,2,3)){ |
3 |
Stream.of(array).map(item -> Strings.padEnd(item, i, '@')).forEach(System.out::println); |
上面的这个例子中,map中的lambda表达式访问外部变量Integer i。并且可以访问外部变量是lambda表达式的一个重要特性,这样我们可以看出来lambda表达式的三个重要组成部分:
不过lambda表达式访问外部变量有一个非常重要的限制:变量不可变(只是引用不可变,而不是真正的不可变)。
1 |
String[] array = {"a", "b", "c"}; |
2 |
for(int i = 1; i<4; i++){ |
3 |
Stream.of(array).map(item -> Strings.padEnd(item, i, '@')).forEach(System.out::println); |
上面的代码,会报编译错误。因为变量i被lambda表达式引用,所以编译器会隐式的把其当成final来处理(ps:大家可以想象问什么上一个例
子不报错,而这个报错。)细心的读者肯定会发现不对啊,以前java的匿名内部类在访问外部变量的时候,外部变量必须用final修饰。Bingo,在
java8对这个限制做了优化(前面说的小小优化),可以不用显示使用final修饰,但是编译器隐式当成final来处理。
lambda眼中的this
在lambda中,this不是指向lambda表达式产生的那个SAM对象,而是声明它的外部对象。
方法引用(Method reference)和构造器引用(construct reference)
方法引用
前面介绍lambda表达式简化的时候,已经看过方法引用的身影了。方法引用可以在某些条件成立的情况下,更加简化lambda表达式的声明。方法引用语法格式有以下三种:
- objectName::instanceMethod
- ClassName::staticMethod
- ClassName::instanceMethod
前两种方式类似,等同于把lambda表达式的参数直接当成instanceMethod|staticMethod的参数来调用。比如
System.out::println等同于x->System.out.println(x);Math::max等同于(x,
y)->Math.max(x,y)。
最后一种方式,等同于把lambda表达式的第一个参数当成instanceMethod的目标对象,其他剩余参数当成该方法的参数。比如String::toLowerCase等同于x->x.toLowerCase()。
构造器引用
构造器引用语法如下:ClassName::new,把lambda表达式的参数当成ClassName构造器的参数 。例如BigDecimal::new等同于x->new BigDecimal(x)。
吐槽一下方法引用
表面上看起来方法引用和构造器引用进一步简化了lambda表达式的书写,但是个人觉得这方面没有Scala的下划线语法更加通用。比较才能看出,翠花,上代码!
1 |
List<String> names = new ArrayList<>(); |
4 |
names.stream().map(name -> name.charAt(0)).collect(Collectors.toList()); |
上面的这段代码就是给定一个String类型的List,获取每个String的首字母,并将其组合成新的List。这段代码就没办法使用方法引用来简化。接下来,我们简单对比一下Scala的下划线语法(不必太纠结Scala的语法,这里只是做个对比):
2 |
List[String] names = .... |
在Scala中基本不用写lambda表达式的参数声明。
下期预告
Java8初体(二)Sream语法详解
引用文档
- 《Java SE 8 for the Really Impatient》
- Java 8 Tutorial
- Java 8 API doc
- java8【一、lambda表达式语法】
特点 lambda表达式允许将函数作为方法的参数 lambda表达式更加简洁 特征 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值. 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需 ...
- Java8初体验(1):lambda表达式语法
原文出处: 一冰_天锦 本文主要记录自己学习Java8的历程,方便大家一起探讨和自己的备忘.因为本人也是刚刚开始学习Java8,所以文中肯定有错误和理解偏差的地方,希望大家帮忙指出,我会持续修改和优化 ...
- Java8初体验(二)Stream语法详解
感谢同事[天锦]的投稿.投稿请联系 tengfei@ifeve.com 上篇文章Java8初体验(一)lambda表达式语法比 较详细的介绍了lambda表达式的方方面面,细心的读者会发现那篇文章的例 ...
- Java8 lambda表达式语法 1
本文主要记录自己学习Java8的历程,方便大家一起探讨和自己的备忘.因为本人也是刚刚开始学习Java8,所以文中肯定有错误和理解偏差的地方,希望大家帮忙指出,我会持续修改和优化.本文是该系列的第一篇, ...
- Java8初体验(2):Stream语法详解
原文出处: 一冰_天锦 上篇文章Java8初体验(1):lambda表达式语法比较详细的介绍了lambda表达式的方方面面,细心的读者会发现那篇文章的例子中有很多Stream的例子.这些Stream的 ...
- Java8初体验(二)Stream语法详解(转)
本文转自http://ifeve.com/stream/ Java8初体验(二)Stream语法详解 感谢同事[天锦]的投稿.投稿请联系 tengfei@ifeve.com上篇文章Java8初体验(一 ...
- Java8初体验(二)Stream语法详解---符合人的思维模式,数据源--》stream-->干什么事(具体怎么做,就交给Stream)--》聚合
Function.identity()是什么? // 将Stream转换成容器或Map Stream<String> stream = Stream.of("I", & ...
- Java8特性详解 lambda表达式 Stream
1.lambda表达式 Java8最值得学习的特性就是Lambda表达式和Stream API,如果有python或者javascript的语言基础,对理解Lambda表达式有很大帮助,因为Java正 ...
- Java8特性详解 lambda表达式 Stream【转】
本文转自http://www.cnblogs.com/aoeiuv/p/5911692.html 1.lambda表达式 Java8最值得学习的特性就是Lambda表达式和Stream API,如果有 ...
随机推荐
- BZOJ3240 [Noi2013]矩阵游戏
本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000作者博客:http://www.cnblogs.com/ljh2000-jump/转 ...
- String.Format将人民币符号改成美元符号{0:C}
经过测试发现这个是和系统语言有关,如果直接使用{0:C}进行转换时会跟系统语言有关,中文则显示人民币,老美就是美元. 那么在使用时,直接增加区域配置:string.Format(new System. ...
- bzoj2765[JLOI2010]铁人双项比赛
题意:铁人双项比赛由长跑和骑自行车组成,参赛选手必须先完成k公里的长跑,然后完成r公里的骑车,才能到达终点.参赛选手有的擅长长跑,有的擅长骑车. 如果总赛程s=k+r一定,那么K越大,对擅长长跑的选手 ...
- 数据结构算法C语言实现(十一)--- 3.4队列的链式表示和实现
一.简介 FIFO. 二.头文件 //3_4_part1.h /** author:zhaoyu email:zhaoyu1995.com@gmail.com date:2016-6-9 note:r ...
- [Android]Volley源码分析(一)
一. 如何使用Volley? 1. 首先定义一个RequestManager类,用来在Android程序启动时对Volley进行初始化.RequestManager为单例类,因为只有在程序启动时调用, ...
- Java线程池的那些事
熟悉java多线程的朋友一定十分了解java的线程池,jdk中的核心实现类为java.util.concurrent.ThreadPoolExecutor.大家可能了解到它的原理,甚至看过它的源码:但 ...
- Beta版本——第三次冲刺博客
我说的都队 031402304 陈燊 031402342 许玲玲 031402337 胡心颖 03140241 王婷婷 031402203 陈齐民 031402209 黄伟炜 031402233 郑扬 ...
- ubuntu下的ssh
ubuntu默认是没有安装openssh-server的,今天简单的写一下ubuntu上ssh的安装与使用. 一.服务器端 安装 apt-get install openssh-server #安装 ...
- 捉襟见肘之UIImagePickerController 和自定义照相机
目标:自拍并结合C++算法,识别人脸信息,第一步骤,调用相机并且自定义界面和转场效果 先复习一下 https://developer.apple.com/library/prerelease/tvos ...
- POJ3292 Semi-prime H-numbers
传送门: 刷<数论一本通>时看到的题,简单记录一下. 题目大意(照抄书上的):形如4n+1的数被称为H数,乘法在H数组成的集合内是封闭的.在这个集合中是能被1和本身整除的数叫H-素数,其余 ...