本文首发于 blog.zhaochunqi.com 转载请注明 blog.zhaochunqi.com
根据 JSR 335 , Java 终于在 Java 8 中引入了 Lambda 表达式。也称之为闭包或者匿名函数。
JSR 335
所谓的 JSR (Java Specification Requests) 全称叫做 Java 规范提案。简单来说就是向 Java 社区提交新的 API 或 服务 请求的提案。这些提案将作为 Java 社区进行 Java 语言开发的需求,引导着开发的方向。
JSR 335 的提案内容摘要如下:
This JSR will extend the Java Programming Language Specification and the Java Virtual Machine Specification to support the following features:
Lambda Expressions SAM Conversion Method References Virtual Extension Methods 也就是如下几点:
支持 lambda 表达式。 支持 SAM conversion 用来向前兼容。 方法引用 Method References Virtual Extension Methods 在 Java 8 中,以上均已经实现,以上内容下文均有介绍。
为什么需要 Lambda 表达式?
Lambda 表达式,其实就是代码块。
如果你想实现根据字符串长度大小来排序,而不是默认的字母顺序,你可以自己来实现一个 Comparator 用来 Sort。
class LengthComparator implements Comparator<String> { public int compare(String first, String second) { return Integer.compare(first.length(), second.length()); Arrays.sort(strings, new LengthComparator());另外一个例子,我选的是 Android 中的点击事件,同样是 Java:
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(MainActivity.this, "Hello World!", Toast.LENGTH_SHORT).show();上面代码有什么问题呢?
上述例子都是在某个类中实现某个接口,然后传递到另外一个方法中作为参数,然后用来执行。
但是本质上,他们要传递的就是接口中那一个方法的实现而已啊!有必要先创建类,再实例化,再传递给调用的位置吗?
因为 Java 是纯面向对象的语言,像其他语言那样随随便便传个方法过来,那可不行,必须要这样。
在其他语言中你可能可以,但是,在Java 中,不可以。
http://harchiko.qiniudn.com/56cabf7011ab6750.jpgJava 设计人员为了 Java 的简洁跟连贯性,一直拒绝为Java添加这种功能。(这也是我喜欢Java而不喜欢Python的原因啊!!!)
经过多年的努力,开发人员终于找到了符合 Java 编程习惯的 Lambda 表达式!
Lambda 表达式语法(Syntax)
考虑下前面的例子:
Integer.compare(first.length(), second.length())first和second都是 String 类型,Java 是强类型的语言,必须指定类型:
(String first, String second) -> Integer.compare(first.length(), second.length())看到没有!第一个 Lambda 表达式诞生了!!输入、输出简洁明了!
为什么叫 Lambda 呢,这个很多年以前,有位逻辑学家想要标准化的表示一些可以被计算的数学方程(实际上存在,但是很难被表示出来),他就用 ℷ 来表示。
重新介绍一下 Java 中 Lambda 表达式的格式:
(参数) -> 表达式
如果计算的结果并不由一个单一的表达式返回(换言之,返回值存在多种情况),使用“{}",然后明确指定返回值。
(String first, String second) -> { if (first.length() < second.length()) return -1; else if (first.length() > second.length()) return 1; else return 0;如果没有参数,则 "()"中就空着。
() -> { for (int i = 0; i < 1000; i++) doWork(); }如果参数的类型可以被推断出,则可以直接省略
Comparator<String> comp = (first, second) // Same as (String first, String second) -> Integer.compare(first.length(), second.length());这里,first和second可以被推断出是 String 类型,因为 是一个 String 类型的 Comparator。
如果单个参数可以被推断出,你连括号都可以省略:
EventHandler<ActionEvent> listener = event -> System.out.println("Thanks for clicking!"); // Instead of (event) -> or (ActionEvent event) ->你可以像对待其他方法一样,annotation,或者 使用 final 修饰符
(final String name) -> ... (@NonNull String name) -> ...永远不要定义 result 的类型,lambda 表达式总是从上下文中推断出来的:
(String first, String second) -> Integer.compare(first.length(), second.length())注意,在lambda 表达式中,某些分支存在返回值,某些不存在返回值这样的情况是不允许的。
如(int x) -> { if (x >= 0) return 1; }这样是非法的。函数式接口(Functional Interfaces/SAM)
要介绍 Java 中 lambda 表达式的实现,需要知道什么是 函数式接口。
什么叫作函数式接口呢(SAM)?
函数式接口指的是只定义了唯一的抽象方法的接口(除了隐含的Object对象的公共方法), 因此最开始也就做SAM类型的接口(Single Abstract Method)。
Lambda 表达式向前兼容这些接口。
Comparable
举个例子 Array.sort:
Arrays.sort(words, (first, second) -> Integer.compare(first.length(), second.length()));Array.sort() 方法收到一个实现了 Comparable<String> 接口的实例。
其实可以把 Lambda 表达式想象成一个方法,而非一个对象,一个可以传入一个接口的方法。
OnClickListener
再举个例子
button.setOnClickListener(event -> System.out.println("Thanks for clicking!"));你看,是不是更易读了呢?
Lambda 表达式能够向前兼容这些 interfaces, 太棒了! 那 Lambda 表达式还能干什么呢?
实际上,将函数式接口转变成 lambda 表达式是你在 Java 中唯一能做的事情。
Why ?!!
在其他的语言中,你可以定义一些方便的方法类型,但在 Java 中,你甚至不能将一个Lambda表达式赋值给类型为 Object 的变量,因为 Object 变量不是一个 Functional Interface。
Java 的设计者们坚持使用熟悉的 interface 概念而不是为其引入新的 方法类型。
(这里我还要为设计者点赞!谨慎的设计,一方面降低了初学者的门槛,一方面方便了高级用户的使用。对比 python2和 python3,升级的不兼容让很多人一直停留在 python2)
Method References
能不能再简洁一点?有的时候我们所要做的事情不过是调用其他类中方法来处理事件。
button.setOnClickListener(event -> System.out.println(event));如果这样呢?
button.setOnAction(System.out::println);表达式
System.out::println属于一个方法引用(method reference), 相当于 lambda 表达式x -> System.out.println(x)class ConcurrentGreeter extends Greeter { public void greet() { Thread t = new Thread(super::greet); t.start();构造方法引用 Constructor References
跟上一个差不多,毕竟构造方法 也是方法啊!!不过方法名字为 new 。
但是!这个构造方法引用有一个牛逼的地方!
你知道 Array 是不能使用范型的对吧!(什么,你不知道?看看这里 http://stackoverflow.com/questions/2927391/whats-the-reason-i-cant-create-generic-array-types-in-java),你没有办法创建一个类型为 T 的 Array 。 new T[n] 将会被覆盖为 new Object[n]。
假设我们想要一个包含 buttons 的 Array。Stream interface 可以返回一个 Object array。
Object[] buttons = stream.toArray();不不不,我们可不想要 Object。Stream 库使用 构造方法引用解决了这个问题:
Button[] buttons = stream.toArray(Button[]::new);这是什么意思呢? 不需要定义为 final,也不能改?
其实理解起来很简单,Java 8 中,不需要定义为 final ,但你其实可以直接把他当作 final,不要试图修改它就行了。
即便你用内部类,现在也无需定义为 final 了。
参考 StackOverFlow 链接: http://stackoverflow.com/questions/4732544/why-are-only-final-variables-accessible-in-anonymous-class
Default Methods
由于历史原因,像是类似 Collection 这种接口,如果进行添加接口的话,那将会造成之前的代码出错。
Java 想了一个一劳永逸的方法解决这个问题, 使用 default 修饰符来提供默认的实现
比如 Collection 接口的源代码:
default void remove() { throw new UnsupportedOperationException("remove");当没有 override remove 这个方法是,调用的时候返回 UnsupportedOperationException 错误。
Static Methods in Interfaces
Java 8 中,你可以在接口中添加静态方法了。 可能与你想的不太一样,但是呢,为了方便起见,现在 interface 可以有静态方法了。
参考链接:
JSR 335: Lambda Expressions for the JavaTM Programming Language Java 8 新特性概述 Lambda Expressions in Java 8