Lambda表达式
Lambda表达式是Java 8引入的一个新机制,它是一个可传递的代码块,可以在以后执行一次或者多次。在以往的Java中,单独传递一段代码是不可能的(万物基于类),因此以往的做法是使用接口或者类的方法,通过实现接口方法或者实例化类对象来传递一段代码。而Java 8之后提出了Lambda表达式机制,使得一段代码的传递方式得到大大简化,比如,对比下面两段代码——
1. 使用Comparator接口函数进行数组排序
/*
* @Author:Jason.Lee
* @Date:2019-8-6
* @Brief:Learn about Java's lambda expression mechanism
* */
import java.util.Arrays;
import java.util.Comparator;
class NoLambda{
public static void main(String[] args){
Integer[] integers = new Integer[]{1,2,3,4,5,6,7,8,9};
System.out.print(Arrays.toString(integers));
Arrays.sort(integers, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
System.out.println();
System.out.print(Arrays.toString(integers));
}
}
2.使用Lambda表达式进行数组排序
/*
* @Author:Jason.Lee
* @Date:2019-8-6
* @Brief:Learn about Java's lambda expression mechanism
* */
import java.util.Arrays;
class UseLambda{
public static void main(String args[]){
Integer[] integers = new Integer[] {1,2,3,4,5,6,7,8,9};
System.out.print(Arrays.toString(integers));
Arrays.sort(integers,(x,y)->y-x);
System.out.println();
System.out.print(Arrays.toString(integers));
}
}
两者都能达到相同的排序效果,显而易见,Lambda的方式更加简洁。这就是Java Lambda机制所要达到的效果,在某些适用的场景下,提供更加简洁的代码块表达方法。
语法总结
Lambda最基本的组成包括括号、箭头以及一个表达式。语法格式形如——
(x)->{
/* lambda expression*/
};
其中,括号中参数可以有零个或者多个,表达式可以为多行。Lambda表达式在不同情形下,使用方法会有所不同,下面总结集中常见的情形。
语法1: (Type x,Type y)->{expression with return};
这是Lambda表达式最完整的形态,x和y表示调用该代码段可能要用到的参数以及对应的类型,表达式代表要对参数进行的处理,我们举个例子,对String类型进行长度排列——
import java.util.Arrays;
class UseLambda{
public static void main(String args[]){
String[] strings = new String[]{"ABC","DE","F"};
System.out.println("original arrays:"+Arrays.toString(strings));
Arrays.sort(strings);
System.out.println("after arrays.sort:"+Arrays.toString(strings));
Arrays.sort(strings,(String x,String y)->{return x.length()-y.length();});
System.out.print("after arrays.sort by lambda expression:"+Arrays.toString(strings));
}
}
String类型扩展了Comparator接口中的comparaTo方法,可以按照字典序排列字符串,但是我们这里定义了自己的排序方法,可以使得字符串按照字符长度排列。
语法2: (x,y)->expression
实际上,虚拟机比我们想象的要机智一些,很多时候,括号中的类型声明可以省去,因为在代码段调用的时候虚拟机会自动推断,如上文中的表达式完全可以写成——
Arrays.sort(strings,(x,y)->{return x.length()-y.length();});
结果完全一致。实际上,如果表达式只有一行,且不带return关键字的话,大括号可以省去,比如,上一行代码可以进一步简化为:
Arrays.sort(strings,(x,y)->x.length()-y.length());
语法3:x->expression
如果参数只有一个,那么小括号也可以省去,比如下列情形——
import java.util.function.Consumer;
class UseLambda{
public static void main(String args[]){
Consumer<String> consumer = c->System.out.println("Hello,"+c);
consumer.accept("Lambda expression!");
}
}
这种写法比较抽象,首先,c->System.out.println("Hello,"+c)
,JVM通过前面的泛型类型<String>
判断c
的类型是String
,然后System.out.println("Hello,"+c)
实现了函数接口accept
,所以这段代码等同于:
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("Hello,"+s);
}
};
consumer.accept("Lambda expression!");
但是显而易见,Lambda更加灵活飘逸。
语法4:()->expression
情形4比情形3更进一步,参数可以为零,但是此时要加上小括号,如果表达式没有return返回值且只有一行,同样可以不带大括号。举个例子——
class UseLambda{
public static void main(String args[]){
Runnable runnable = ()->System.out.println("thread run!");
runnable.run();
}
}
这段代码等同于——
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("thread run!");
}
};
runnable.run();
重写run方法来开启线程,是我们常用的手段,但现在可以用Lambda表达式的方法来取代之前使用匿名类实现接口方法的方式(多行只需要加大括号就行了),显然Lambda的方式更简洁。
函数式接口
对于自由一个抽象方法的接口,我们成之外函数式接口(functional interface),从前面的例子不难看出,Lambda表达式发挥作用的场合大多是函数式接口,实际上,在Java中,Lambda表达式可以转换为函数式接口。下表是一个常见函数式接口的列表——
序号 | 函数式接口 | 参数类型 | 返回类型 | 抽象方法 | 描述 |
---|---|---|---|---|---|
1 | Runnable | void | void | run | 作为无参数或返回值的动作运行 |
2 | Supplier | 无 | T | get | 提供一个T类型的值 |
3 | Consumer | T | void | accept | 处理一个T类型的值 |
4 | Function<T,R> | T | R | apply | 有一个T类型的函数 |
5 | Predicate | T | boolean | test | 布尔值函数 |
当然也可以设计自己的函数式接口,但在编写的时候务必添加注解@FunctionalInterface
与C++ Lambda表达式的对比
Java 8提供的Lambda表达式与C++ 11的Lambda表达式相比,无论是形式上还是功能上,都高度相似(关于C++ Lambda表达式的介绍,可参加另一篇博客)。可能是为了弥补Java在面向过程上的不足,Java的设计者建议将Lambda看作一个函数,而非对象,但两者在不同的语言特性中又表现出不同的性能,C++11所提供的Lambda表达式更多服务于STL库中各种容器以及算法上,而Java的Lambda表达式更多应用于函数式接口。但受限于语言特性,Java的Lambda表达式只能使用对象作为中间参数,而C++的Lambda相对而言就神通广大得多,它既能应用于普通函数、类、泛型、STL容器、仿函数,也能使用指针、引用、基本类型等,方方面面,几乎无所不能。