java是一门纯面向对象的语言,如果我们需要传递某个可以复用的代码块,我们必须将代码块逻辑抽离到对象中。比如在调用Arrays.sort实现数组排序时,如果有compare代码块逻辑,我们必须将其抽离到Comparator接口中,并传入Arrays.sort:
class MyComparator implements Comparator<Integer>{
public int compare(Integer i1, Integer i2){
return i1 - i2 ;
}
}
....
Arrays.sort(array , new MyComparator) ;
在数组完成排序之前,compare方法会不断被sort方法调用。这个可复用代码块的核心逻辑其实只有一行return i1-i2; ,但是我们却写了一个类、new了一个实例来将其传递到调用到位置。这是极不方便的。而在java8引入lambda后,java也有了直接处理代码块的能力,在一定程度上支持了函数式编程。
现在上述数组排序可以这样实现:Arrays.sort(array , (int i1 , int i2) -> i1-i2))。可以看到,通过lambda,我们能直接将代码块传入调用的位置。
lambda表达式结构 : (参数) -> {代码块}
() -> {System.out.println("test")}
() -> System.out.println("test")
Comparator comp = (i1 , i2) -> i1-i2 ;
(int x) - { if(x > 0) return 1 ;}
通俗地说:只有一个抽象方法的接口成为函数式接口。
当我们需要一个函数式接口的对象时(如第二节中的第3点)我们可以直接提供一个lambda表达式,而不用实现接口、new对象。
在第一节的Arrays.sort(array , (int i1 , int i2) -> i1-i2 )
中,Aarrays.sort底层其实是接受了一个实现了Comparator
的接口,在sort调用接口的compare方法时,执行的是传入的lambda体。 从某种意义上说,是编译器将我们的lambda表达式自动封装进了函数式接口中,并并初始化了一个实例,显示出来就是“lambda表达式能转换成函数式接口的实例”:
Comparator
;
如果现存的方法已经能够完成某个动作时,我们可以在需要函数式接口实例的位置,直接引用该方法,而不需要再写lambda表达式。
比如 Integer类中有静态方法:
Integer.compare(i1 , i2)
该方法是用来比较两个整数的方法,源码如下:
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
该方法可以实现比较两个整数,并返回int类型的值,符合比较器的逻辑。那么我们在实现Arrays.sort时可以这样写:
Arrays.sort(array , Integer::compare)
即引用Integer的compare方法。
三种方法对比:
'''第一种,实现接口'''
class MyComparator implements Comparator<Integer>{
public int compare(Integer i1, Integer i2){
return i1 - i2 ;
}
}
....
Arrays.sort(array , new MyComparator()) ;
'''第二种,使用lambda表达式'''
Arrays.sort(array
, (int i1 , int i2) -> (x < y) ? -1 : ((x == y) ? 0 : 1) ) ;
'''第三种,使用方法调用'''
Arrays.sort(array,Integer::compare);
必须注意的是在第三种方式中,没有像lambda表达式一样显式的声明方法的参数和返回值 ,这是因为编译器能够从引用中获得该方法的参数和返回值,并与原本的函数式接口中的方法作对比。
因此:引用的方法的参数数量和类型、返回值都必须和原来函数式接口中的方法一样。比如Integer.compare和Comparator.compare方法在参数的数量类型、返回值类型上一致,如下:
// Integer.compare的定义
public static int compare(int x, int y)
// Compatator.compare的定义
int compare(T o1, T o2);
所以方法引用Integer::compare 能够转换成Comparetor接口的实例。
方法引用有三种形式:
lambda可以在代码体中访问lambda表达式外的变量,如下所示:首先定义了一个函数式接口,然后在第8行代码,使用将printMsg方法的参数写入到lambda代码体中。
@FunctionInterface
interface MyfunctionIT{
void print() ;
}
public static void printMsg(String text)
{
repeatPrint(() -> System.out.println(text));
}
public void repeatPrint(MyfunctionIT printIt){
for(int i= 0 ; i<100 ; i++){
printIt.print();
}
}
值得注意的是lambda表达是引用的外部变量不可以改变,这和局部内部类的特性有关。
lambda表达式的重点是延迟执行。在我们需要多次运行代码、回调时再运行代码等等非立即执行的时候,就可以将代码块包装成lambda表达,转化为一个函数式接口实例。比如某个动作想要重复10次:
repeat(10 , ()-> System.out.println("Hello , World !"))
要接收这个lambda表达式,我们就需要一个接口:
reapet(int n , Runable action){
for(int i = 0 ; i < n ; i++){
action.run();
}
}
在这里,我们使用了Runable接口。当acrion.run执行时,会执行我们传入的lambda表达式。
在进行方法设计时,我们可以利用系统提供的函数式接口:
值得注意的是,在高性能需求的场景下。对于基本类型的参数传递,我们需要注意减少自动装箱的开销。比如将接口Consumer 替换为不需要包装类型的IntConsumer接口:
interface Consumer<Integer>{
void accept(Integer i);
}
@FounctionalInterface
interface IntConsumer{
void accept(int i);
}
如果自定义函数式接口最好使用@FounctionalInterface定义,一是做标识,二是确保接口只有一个抽象方法。
Comparator中提供了很多静态方法来创建比较器。比如:comparing方法能够提取类型T中一个键s,将该类型T转化为按照键s可比较的(Comparable)。假设有一个Person对象数组,可如下按名字对数组进行排序:
Arrays.sort(people ,
Comparator.comparing(Person::getFirstName)
.thanComparing(Persion::getSecondName));
comparing函数接收的是一个方法引用,返回的也是一个比较器实例。Person::getFirstName称为键函数。
上述例子的结果是,先用FirstName排序,如果相同,再依照SecondName排序。默认按照字典序排序。
如果我们需要对名字按照长度排序如何自定义呢?如下所示,可以在comparing方法的第二个参数位置传入一个自定义比较器:
Arrays.sort(people ,
Comparator.comparing(Person::getFirstName , (s,t) -> s.length - t.length);
这里传入的比较器使用的是lambda表达式。
对于上述动作,也可以直接使用变体Arrays.sort(people , Comparator.comparInt((p) -> p.getName().getlength()))
如果键函数可以返回null,还需要用到nullsFirst和nullsLast适配器。这里不赘述,需要了解的读者可以自行了解。
参考文章Lambda表达式实现原理分析
public class LambdaTest {
public static void printString(String s, Print<String> print) {
print.print(s);
}
public static void main(String[] args) {
printString("test", (x) -> System.out.println(x));
}
}
@FunctionalInterface
interface Print<T> {
public void print(T x);
}
经过反编译分析还原:
public class LambdaTest {
public static void PrintString(String s, Print<String> print) {
print.print(s);
}
public static void main(String[] args) {
PrintString("test", new LambdaTest$$Lambda$1()); // 传入内部类实例
}
//lambda表达式代码逻辑的私有静态方法
private static void lambda$main$0(String x) {
System.out.println(x);
}
// 生成内部类,内部类实现了接口
static final class LambdaTest$$Lambda$1 implements Print {
public void print(Object obj) {
LambdaTest.lambda$main$0((String) obj); //调用lambda私有静态方法
}
private LambdaTest$$Lambda$1() {
}
}
}
@FunctionalInterface
interface Print<T> {
public void print(T x);
}
过程:
《java核心技术-卷1》