背景
Java是一门面向对象的语言, 所有的数据和行为都包装到了类中, 以成员变量或成员方法的形式展现,从语言设计的角度来看, 这样做的好处显而易见,我们可以用一个个的类来包装各种事物, 无论是物质世界的实际存在,像牛 羊 鸡 鸟,还是程序世界的各种组件,如按钮, 文本, 我们都可以用一个包含其特定数据及行为的类来描述。
但是,在采用java实际构建软件的过程中, 我们发现, 很多时候, java的类(接口)只是对单个行为的封装, 在实例化这个行为所在对象的时候, 我们会发现代码特别繁琐笨重, 从可读性上来讲,代码逻辑也不够直观。
典型的场景,在GUI编程中, 我们想要点击按钮的时候,执行某种动作, 需要写如下的代码:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
println("button clicked");
}
});
这就是java中比较经典的高度问题(vertial problem), 相信很多同学都写过这种让人心里不爽的代码。
再看一下lambda表达式的写法:
button.addActionListener(event -> println("button clicked"));
函数式接口
在正式介绍Java 8的lambda表达式之前, 必须先介绍一下Java 8的函数式接口概念
首先, 函数式接口并不是Java 8中的新语法, 它只是一个概念。 函数式接口是完完全全的Java Interface, 从语法上来讲和Java 8之前的Interface是没有任何区别的。
首先看一下Jdk中的Runnable接口, 它就是一个函数式接口。
public interface Runnable {
public abstract void run();
}
只包含一个抽象方法的接口, 我们称之为函数式接口。
之所以将这种形式的接口单独形成函数式接口的概念, 是因为Java函数式接口和Java 8 中的lambda表达式是紧密结合在一起的
我们可以利用lambda表达式来创建函数式接口的对象。
匿名内部类的写法:
new Thread(new Runnable() {
@Override
public void run() {
doSomeThing();
}
}).start();
lambda表达式写法:
new Thread(
() -> dosomething()
).start();
可以看出, 相比我们使用匿名内部类的形式实例化一个接口的对象, lambda表达式在代码上更简洁, 在良好的编码习惯下, 可读性更好。
小贴士:
如果查看过JDK 1.8中Runnable接口文件的同学, 会发现代码中多出了一个注解: @FunctionalInterface
,
这个注解并不是必须的, 在函数式接口中加入它有两个好处:
1. javac(编译器)会检查标注该注解的接口, 是否符合函数式接口的规范, 如果不符合, 则在编译器就能抛出错误
2. 在javadoc中也会包含一条声明, 说明该接口是一个函数式接口
lambda表达式
这一小节,我们关注两个问题, lambda表达式的类型和写法。
类型
Java中的lambda表达式是有类型的, 每个lambda表达式的类型对应着一个函数式接口,简单的说, lambda表达式可以和函数式接口的实现(其实就是匿名内部类啦)相互转换,这里说的转换只的是代码上的替换, 并非编译期的转换。
写法
lambda表达式的几种基本写法:
Runnable noArgument = () -> System.out.println("Hello, world!");
ActionListener onArgument = event -> System.out.println("button clicked");
Runnable multiStatement = () -> {
System.out.print("Hello, ");
System.out.println(" world");
};
BinaryOperator<Long> add = (x, y) -> x + y;
BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y; //显式声明类型参数
lambda表达式可以分为两个部分, 一个是表达式参数,用()
包含; 二是代码块, 用{}
包含。 这两部分使用->
分开。
在我们写lambda时一般只需要关注这两部分即可
参数
lambda表达式的参数部分: 参数的类型、及()
都可以酌情省略。
注意:
如果lambda表达式对应的接口抽象方法没有参数,
()
不可省略有一个参数时,
()
可以省略, 参数类型酌情省略, 之所以说酌情省略, 原因有二, 第一个原因在月之暗面中有涉及到, 第二个原因是要在代码简洁性与避免代码异味求得平衡形如: 1).
(ActionEvent event) ->{}
2).(event) -> {}
3).event -> {}
参数大于一个,
()
不可省略, 参数类型酌情省略, 原因同2
代码块
这里我们需要注意一点,就是当lambda表达式对应函数式接口中的抽象方法存在返回值, 并且代码块中仅有返回语句, 那么可以省略return关键字。