lambda表达式

lambda表达式

lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。

为什么要用

Java是面向对象语言,想要传递一个代码段则需要创建一个对象,这个对象的类需要有一个方法能够包含所需的代码。

在某些情况中,需要把一段代码传递给某个对象,但是,由于java是面向对象的,所以,这段代码需要包装在一个类当中举例:

1
2
3
4
/**
* 需要一个数组参数与一个实现了Comparator接口的类
*/
public static <T> void sort(T[] a, Comparator<? super T> c)

因此,如果我们想要去使用这个sort,那么就需要去构造一个实现了该接口的类,然后去重写方法,将这个类传递给sort才行。

1
2
3
4
5
6
7
8
/**
* 构造一个实现Comparator接口的类
*/
class LengthComparator implements Comparator<String>{
public int compareTo(String f,String s){
return f.length()-s.length();
}
}

如此,我们才能够实现按照字符串长度来排序。可以看到的是,在这个类当中内容并不是很多,只是一个代码块而已。sort()需要的也只是那个compareTo方法。

适用范围

  • 对于只有一个抽象方法的接口,需要这种接口的对象时就可以提供一个lambda表达。

使用lambda

其具体语法为:

(类型 参数,类型 参数)->{表达式}

那么最终我们需要的代码块是:

1
2
3
4
5
6
7
8
f.length()-s.length();
//java作为强类型,则需要指明类型:
(String f,String s)
->f.length()-s.length()
//以上便是一个完整的lambda表达式
//完整的调用一次
Arrays.sort(planets, (String first,String second)
-> first.length() - second.length());

lambda既然是代码块,当然也就不只一行

1
2
3
4
(String f,String s)->{
//随便写,就像一个方法一样
return f.length()-s.length();
}

一些特殊写法

经常会看到一些和上面的示例并不相同的写法

  1. 没有写类型参数
1
2
3
4
5
6
7
/**
* planets是一个String数组
* 因此编译器可以推导出其后面的应当是一个String类型的参数
* 所以省略了参数
*/
Arrays.sort(planets, (first,second)
-> first.length() - second.length());
  1. 没有写()
1
2
3
4
5
/**
* 方法只有一个参数,并且参数类型可以推导出来
*/
ActionListener listener = event - >
System.out.println ( " The time is " + new Date ( ) " ) ;

处理lambda表达式

lambda表达式的重点是“延时执行”,之所以如此是因为

  • 在一个单独的线程运行代码
  • 多次运行代码
  • 在算法的适当位置运行代码(例如排序)
  • 发生某种情况时运行代码
  • 只在必要时候运行

注意

即便这个lambda不需要任何的方法参数,依然要写()

1
2
3
4
()->{
int i=0;
System.out.println(i);
}

lambda表达式可以访问外围方法或类当中的变量,但是这些变量必须是最终变量,不能随意改变,也不可以在表达式内改变

函数式接口

定义

对于只有一个抽象方法的接口,需要这种接口的对象时就可以提供一个lambda表达式,这种接口为函数式接口。

方法引用

对于已经存在的方法,可以完成想传递给其他代码的某个动作,代替了代码块,则可以使用方法引用

1
2
3
4
Timer t = new Timer ( 1000 , System.out:: println );

//等价于:
Timer t = new Timer ( 1000 ,x-> System.out.println(x) );

使用:

  • object :: instanceMethod
  • Class :: staticMethod
  • Class :: instanceMethod

构造器引用

与方法引用类似,不过方法名为new,例如Person :: new是Person构造器的一个引用,具体是那个引用取决于上下文

变量作用域

可能希望在lambda表达式中访问外围方法或类中的变量。

1
2
3
4
5
6
7
public static void repeadMsg(String text,int deploy){
ActionListener listener = event ->{
System.out.println(text);
Toolkit.getDefaultToolkit.beep();
};
new Timer(deploy,listener).start();
}

这个代码当中,lambda用到了外围的变量text,而这么使用可能会存在问题,即lambda代码可能会在repeatMessage调用返回很久后才运行,此时参数变量已经不存在了,那如何保留text呢

lambda表达式有3个部分

  • 一个代码块
  • 参数
  • 自由变量的值
    • 指非参数而且不在lambda中定义的变量。例如上述的text变量。在该示例当中,text变量被lambda捕获。即这个变量的值被复制到这个对象的实例变量中
    • 要明确所捕获的值是明确定义的,即只能引用值不会改变的变量,当在lambda中进行text++,则是不允许的。如果这个值在lambda外部改变,也是不合法的
    • 即lambda捕获的变量必须实际上是最终变量,即初始化后就不会再为它赋新值。

lambda表达式的函数体拥有与嵌套块相同的作用域,适用命名冲突和遮蔽的有关规则,即lambda内部声明的变量与一个局部变量不可重名等。

this在lambda中指创建这个lambda表达式的方法的this参数,即该对象。

参考