Lambda表达式是怎样确定它所实现的是哪一个函数式接口的?
在上一章我们讲解Lambda表达式的时候很多人可能会产生疑问,Lambda表达式是怎样确定它实现的是哪一个函数式接口呢?它又是怎样确定自己参数列表中的参数类型呢?
这两个问题我们一起讲解,先看看下面的例子:
@FunctionalInterface
public interface MyInterface<T> {
public T add(T t , List<T> list);
}
为了方便讲解,我在自定义的函数式接口的抽象方法中定义了两个形参
public class one<T> {
public static void main(String[] args){
test((m,n) -> {n.add(m);return n;});
/* test((m,n) -> {m.add(n);return n;}); */
}
public static void test(MyInterface myInterface){
List<String> list = (List<String>) myInterface.add("hello Lambda" ,new ArrayList<String>());
}
}
然后我在主方法中调用test方法并传入了一个Lambda表达式,那么显而易见,在调用test方法的时候就已经确定了你所传入的Lambda表达式是实现了MyInterface这个接口,现在就确定了这个Lambda表达式的目标类型是MyInterface<T>;然后接着推断Lambda表达式中参数列表的参数类型,知道了目标类型后我们就已经确定参数列表中的第一个参数可以是任意类型,第二个参数必须是List<T>类型,也就是上述代码中的m是任意类型,n是List<T>类型,最后判断Lambda表达式的返回值是否和接口中定义的抽象方法相同。
如果你在上述代码中将主方法改为注释部分的化会报错,那是因为在确定了目标类型后Lambda表达式的参数列表必须和函数式接口的抽象方法的参数列表严格一致(类型,顺序),因为你的第一个参数是泛型类型,所以编译器不确定他是否拥有add()方法,这是由于Java泛型的特性引起的。
在Lambda表达式中还可以使用变量:
举个栗子:
public class one<T> {
public static void main(String[] args){
String str = "hello Lambda";
test((m,n) -> {
n.add(m);
n.add(str);
return n;
});
}
public static void test(MyInterface myInterface){
List<String> list = (List<String>) myInterface.add("hello Lambda" ,new ArrayList<String>());
for (String s :
list) {
System.out.println(s);
}
}
}
在上面代码中我们使用的还是MyInterface接口,但是在第六行我加入了一个操作:n.add(str); 这个str是我们在主方法中定义的字符串,从这里可以看出在Lambda中我们可以使用类中的变量,就像匿名类一样,这种Lambda我们称之为捕获Lambda。
Lambda中不仅可以使用成员变量,还可以使用局部变量,但是,我们在Lambda中使用局部变量时要注意,这个被使用的局部变量必须是final类型(在上述代码中我使用的是String类型,而String类型本身就是final类型的)。
为什么在Lambda 中只能使用final类型的局部变量呢?
我们知道,在多线程中局部变量永远是线程安全的,因为它保存在栈中,而每一个线程都有自己的栈内存,所以不存在线程安全问题,我们在使用Lambda表达式的时候可能会使用到多线程,然而在多线程中如果我们的Lambda对捕获的局部变量进行修改的话就不符合多线程的特性,所以Lambda在访问捕获的局部变量时必须访问它最终的值,也就是说这个局部变量必须是final类型的或者实际上就是final类型(例如String类型)。
到此,我们对于行为参数化的学习也结束啦,本人是小白大学生一枚,如有不对或者不当之处,还请前辈们指点指正,谢谢~