感谢同事【天锦】的投稿。投稿请联系 tengfei@ifeve.comjava
本文主要记录本身学习Java8的历程,方便你们一块儿探讨和本身的备忘。由于本人也是刚刚开始学习Java8,因此文中确定有错误和理解误差的地方,但愿你们帮忙指出,我会持续修改和优化。本文是该系列的第一篇,主要介绍Java8对屌丝码农最有吸引力的一个特性—lambda表达式。express
工欲善其器必先利其器,首先安装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 expression”的解释:并发
a function (or a subroutine) defined, and possibly called, without being bound to an identifier。app
简单点说就是:一个不用被绑定到一个标识符上,而且可能被调用的函数。这个解释还不够通俗,lambda表达式能够这样定义(不精确,本身的理解):一段带有输入参数的可执行语句块。这样就比较好理解了吧?一例胜千言。有读者反馈:不理解Stream的含义,因此这里先提供一个没用stream的lambda表达式的例子。ide
//这里省略list的构造 List<String> names = ...; Collections.sort(names, (o1, o2) -> o1.compareTo(o2));
//这里省略list的构造 List<String> names = ...; Collections.sort(names, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } });
上面两段代码分别是:使用lambda表达式来排序和使用匿名内部类来排序。这个例子能够很明显的看出lambda表达式简化代码的效果。接下来展现lambda表达式和其好基友Stream的配合。函数
List<String> names = new ArrayList<>(); names.add("TaoBao"); names.add("ZhiFuBao"); 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以前,若是咱们想作上面代码的操做应该怎么办。优化
先看看普通青年的代码:this
List<String> names = new ArrayList<>(); names.add("TaoBao"); names.add("ZhiFuBao"); List<String> lowercaseNames = new ArrayList<>(); for (String name : names) { lowercaseNames.add(name.toLowerCase()); }
接下来看看文艺青年的代码(借助Guava):
List<String> names = new ArrayList<>(); names.add("TaoBao"); names.add("ZhiFuBao"); List<String> lowercaseNames = FluentIterable.from(names).transform(new Function<String, String>() { @Override public String apply(String name) { return name.toLowerCase(); } }).toList();
在此,咱们再也不讨论普通青年和文艺青年的代码风格孰优孰劣(有兴趣的能够去google搜索“命令式编程vs声明式编程”)。本人更加喜欢声明式的编程风格,因此偏好文艺青年的写法。可是在文艺青年代码初看起来看起来干扰信息有点多,Function匿名类的构造语法稍稍有点冗长。因此Java8的lambda表达式给咱们提供了建立SAM(Single Abstract Method)接口更加简单的语法糖。
咱们在此抽象一下lambda表达式的通常语法:
(Type1 param1, Type2 param2, ..., TypeN paramN) -> { statment1; statment2; //............. return statmentM; }
从lambda表达式的通常语法能够看出来,仍是挺符合上面给出的非精确版本的定义–“一段带有输入参数的可执行语句块”。
上面的lambda表达式语法能够认为是最全的版本,写起来仍是稍稍有些繁琐。别着急,下面陆续介绍一下lambda表达式的各类简化版:
参数类型省略–绝大多数状况,编译器均可以从上下文环境中推断出lambda表达式的参数类型。这样lambda表达式就变成了:
(param1,param2, ..., paramN) -> { statment1; statment2; //............. return statmentM; }
因此咱们最开始的例子就变成了(省略了List的建立):
List<String> lowercaseNames = names.stream().map((name) -> {return name.toLowerCase();}).collect(Collectors.toList());
当lambda表达式的参数个数只有一个,能够省略小括号。lambda表达式简写为:
param1 -> { statment1; statment2; //............. return statmentM; }
因此最开始的例子再次简化为:
List<String> lowercaseNames = names.stream().map(name -> {return name.toLowerCase();}).collect(Collectors.toList());
当lambda表达式只包含一条语句时,能够省略大括号、return和语句结尾的分号。lambda表达式简化为:
param1 -> statment
因此最开始的例子再次简化为:
List<String> lowercaseNames = names.stream().map(name -> name.toLowerCase()).collect(Collectors.toList());
使用Method Reference(具体语法后面介绍)
//注意,这段代码在Idea 13.0.2中显示有错误,可是能够正常运行 List<String> lowercaseNames = names.stream().map(String::toLowerCase).collect(Collectors.toList());
咱们前面全部的介绍,感受上lambda表达式像一个闭关锁国的家伙,能够访问给它传递的参数,也能本身内部定义变量。可是却历来没看到其访问它外部的变量。是否是lambda表达式不能访问其外部变量?咱们能够这样想:lambda表达式实际上是快速建立SAM接口的语法糖,原先的SAM接口均可以访问接口外部变量,lambda表达式确定也是能够(不但能够,在java8中还作了一个小小的升级,后面会介绍)。
String[] array = {"a", "b", "c"}; for(Integer i : Lists.newArrayList(1,2,3)){ Stream.of(array).map(item -> Strings.padEnd(item, i, '@')).forEach(System.out::println); }
上面的这个例子中,map中的lambda表达式访问外部变量Integer i。而且能够访问外部变量是lambda表达式的一个重要特性,这样咱们能够看出来lambda表达式的三个重要组成部分:
输入参数
可执行语句
存放外部变量的空间
不过lambda表达式访问外部变量有一个很是重要的限制:变量不可变(只是引用不可变,而不是真正的不可变)。
String[] array = {"a", "b", "c"}; for(int i = 1; i<4; i++){ 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表达式产生的那个SAM对象,而是声明它的外部对象。
方法引用
前面介绍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的下划线语法更加通用。比较才能看出,翠花,上代码!
List<String> names = new ArrayList<>(); names.add("TaoBao"); names.add("ZhiFuBao"); names.stream().map(name -> name.charAt(0)).collect(Collectors.toList());
上面的这段代码就是给定一个String类型的List,获取每一个String的首字母,并将其组合成新的List。这段代码就没办法使用方法引用来简化。接下来,咱们简单对比一下Scala的下划线语法(没必要太纠结Scala的语法,这里只是作个对比):
//省略List的初始化 List[String] names = .... names.map(_.charAt(0))
在Scala中基本不用写lambda表达式的参数声明。
引用文档
《Java SE 8 for the Really Impatient》
Java 8 Tutorial
Java 8 API doc
原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文连接地址: Java8初体验(一)lambda表达式语法