Java8新特性 - Lambda表达式
提到Java8就不得不说Lambda表达式,它绝对是一项里程碑式的特性,通过Lambda表达式支持了函数式编程,大幅提升编程效率,减少冗余代码。
Lambda表达式
什么是Lambda表达式
通俗来说,Lambda表达式可以理解为简写的方法(method),有参数,有方法体,有返回值,只是没有方法名称(有没有觉得很像匿名内部类?留个悬念往下看),使用表达式的形式使代码更简洁紧凑。lambda表达式语法如下:
(parameters) -> expression
// 或:
(parameters) ->{ statements; }lambda表达式与普通方法有以下区别:
- 参数的类型是可选的,一般情况都不需要显式声明类型
- 参数只有1个时圆括号是可选的,参数个数超过1个则必须要圆括号
- 当主体只包含一条语句时,花括号{}是可选的,建议不加花括号使代码更简洁
- 可选的return语句,表达式的运算结果就是返回值
for example
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)怎么调用lambda?
前面提到Lambda表达式也是方法(method)但没有方法名称,那应该如何调用呢?这需要引入Java8的另一个新特性:函数式接口。
函数式接口
函数式接口(Functional Interface)是有且只有一个抽象方法的接口,但允许有多个非抽象方法。如:
@FunctionalInterface
interface HelloService
{
void sayHello(String message);
}这么看好像函数式接口并不是什么新东西,例如Runnable接口只包含一个run()方法,在Java8中也是函数式接口。
函数式接口与lambda
大家知道在Java中,一切都是对象。Lambda表达式是什么类型的对象?答案就是函数式接口,lambda表达式可以赋值给一个函数式接口,例如:
// lambda赋值给函数式接口
HelloService helloService = (s) -> System.out.println(s);
// 调用lambda表达式
helloService.sayHello("Hello Lambda!");当然并不是任意的表达式都可以赋值,必须保证lambda表达式的参数个数、返回值类型与函数式接口一致。
当lambda表达式可以赋值给一个interface时(或者说是interface的一个实现),就意味着它可以作为参数传递给其他的方法,这不得不说是Java语言的一个革命性进展。
lambda与匿名内部类
回顾一下lambda表达式与匿名内部类,有诸多相似之处:
- 同样都是要实现抽象方法
- 都是匿名的
- 都可以直接作为方法参数
诸多相似之处甚至让人觉得推出lambda表达式就是为了替代匿名内部类,而事实上确实可以这么做(只要保证interface是函数式接口,即只有一个抽象方法,例如:
// lambda表达式可以直接替换java7之前的匿名内部类
// 以Runnable接口为例
// java7之前的匿名内部类:
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("java7之前需要匿名内部类");
}
});
t1.start();
// java8开始可以使用lambda表达式
Thread t2 = new Thread(() -> System.out.println("lambda表达式替换掉Runnable接口的匿名内部类"));
t2.start();代码瞬间减少了!
@FunctionalInterface
前面在定义函数式接口时用到了@FunctionalInterface注解,实际上这个注解只用于编译期检查,以避免一个函数式接口中存在多个抽象方法或未定义抽象方法,但不强制函数式接口一定要加该注解。出于规范性考虑还是建议在函数式接口上使用该注解。
官方提供的函数式接口
在JDK8之前已有一些接口默认符合函数式接口的定义,例如:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.util.Comparator
- java.io.FileFilter
在JDK8中,新增了java.util.function包,其中有若干函数式接口,其接口命名有一定规律,现总结如下:
| 接口类型 | 描述 |
|---|---|
| Bi*** | 表示接受两个入参 |
| ***Consumer | 表示接受入参,但没有返回 |
| ***Supplier | 表示不需要入参,返回一个结果 |
| ***Predicate | 表示接受输入参数,返回一个布尔值 |
| ***Function | 表示接受一个输入,返回另一个对象 |
| ***Operator | 表示入参出参类型都相同的操作,例如加减乘除等运算操作 |
举例如下:
public class FunctionalInterfaceDemo {
public static void main(String[] args) {
// IntPredicate接口表示输入一个int,返回一个boolean
// 例如用于判断一个数是否是偶数
IntPredicate filterEven = i -> i % 2 == 0;
// Consumer<String>接口表示接受一个String参数,但不需要返回值,但方法体中可以实现其他逻辑
// 例如计算并输出字符串的字符长度
Consumer<String> echoLength = s -> System.out.println(s.length());
User user = new User("Louis");
// 这里把lambda表达式作为参数传给函数式接口Function,表示输入一个User参数,返回name字段
echoUserInfo(user, u -> u.getName());
}
// Function<User, String>接口表示接受一个User作为参数,并返回一个String
private static void echoUserInfo(User user, Function<User, String> function) {
System.out.println(function.apply(user));
}
}
class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}