lambda 表达式学习笔记
目录
- 函数式接口
- lambda 表达式简介
- 理解lambda表达式——匿名内部类
- lambda表达式语法
- lambda行为参数化
- lambda表达式的作用域
- this 和 super ,lambda表达式真不是匿名内部类
- 变量作用域
- java8+中的函数式接口
函数式接口
如果一个Java
接口类包含且仅包含一个无默认实现的方法定义,那么他被称为函数式接口。这个方法定义了接口的预期用途。
@FunctionalInterface
注解指示一个接口类是一个函数接口,不允许有多个没有默认实现的接口,但不强制要求在接口上添加该注解
示例1:定义一个函数式接口
@FunctionalInterface
public interface MyFunctionalInterface {
// the single abstract method
void function();
}
示例1中有且仅有没有默认实现的方法定义——function()
, 因此它是一个功能接口。
lambda 表达式简介
理解lambda表达式——匿名内部类
lambda
表达式本质上是一个匿名函数,用于实现一个函数接口定义的功能(不能理解的话可以看作是一个匿名对象,后面会专门说区别)。
lambda表达式语法
lambda
表达式的一般语法是:
(Parameters) -> { Body }
Parameters
为函数的入参,放在()
中,与方法定义相同,lambda
表达式的实现放在{}
中
注意
- 参数的类型声明是可选的,编译器可以统一识别参数类型。明确声明参数类型的
lambda
表达式称为显示lambda
表达式,反之称之为隐式lambda
表达式 ()
是可选的,当参数有且仅有一个时,可以省略小括号,否则必须保留{}
是可选的,如果实现的主题只有一条语句,则可以省略,反之必须保留- 返回关键字
retuen
是可选的,在省略{}
的同时,如果表达式有返回值可以省略关键字retuen
示例2:
// 给定一个值x,返回其的3倍
(int x) ->{return x*3;};
// 省略可选项后定义如下:
x -> x * 3;
lambda行为参数化
可以将lambda
表达式作为参数传递给方,就像传递一个普通的对象一样
示例3:
public class Test {
public static void main(String[] argv) {
doWork((x,y)-> x + y, 4, 2);
}
// (x,y)-> x + y 即为Function 函数接口的实现
private static int doWork(Function function, int x, int y){
return function.cal(x,y);
}
// 定义一个函数接口, 对两个整形进行计算
@FunctionalInterface
interface Function{
int cal(int x, int y);
}
}
lambda表达式的作用域
this 和 super ,lambda表达式真不是匿名内部类
lambda
表达式使用时不会生成新的对象,也就不会产生自己的范围。
如果lambda
表达式中使用关键字this
和super
,他的行为与在执行lambda
表达式的方法中使用this
和super
的行为完全相同。
示例4:
public class Test {
public static void main(String[] argv) {
Test test = new Test();
test.test();
}
private void test(){
MyFunctionalInterface functionNewClass = new MyFunctionalInterface(){
@Override
public void function(int x) {
System.out.println("匿名内部类this======= "+ this);
}
};
MyFunctionalInterface functionLambda = x -> System.out.println("lambda表达式中执行this ========== " + this);
System.out.println("方法中执行this======= "+ this);
functionNewClass.function(1);
functionLambda.function(1);
}
}
// 执行结果
方法中执行this======= cn.bmilk.Test@4bf558aa
匿名内部类this======= cn.bmilk.Test$1@2d38eb89
lambda表达式中执行this ========== cn.bmilk.Test@4bf558aa
示例3的结果显示,lambda
表达式中调用this
指针和在方法test()
中调用this
指代的是同一个对象cn.bmilk.Test@4bf558aa
, 而使用匿名内部类this
指代的是当前匿名内部类产生的对象cn.bmilk.Test$1@2d38eb89
,所以lambda表达式和匿名内部类是不同的
变量作用域
lambda
表达式可以使用`标记了`final
的外层局部变量,这就是说在lambda
表达式中不能修改外层定义的局部变量(不包含全局变量),否则会编译错误。
示例5:引用外层局部变量
private void test(){
int num = 2;
MyFunctionalInterface functionLambda = x -> System.out.println(x * num);
functionLambda.function(1);
}
示例5:
test*()
中定义了一个变量num
,并且在functionLambda
中进行了引用。前面提到lambda
表达式使用外层局部变量,外层局部变量需要定义为final
,但这里并没又定义为final
,这是因为num
虽然没有被定义为final
,但是在num
被定义之后并没有再次改变,看起来和一个被final
修饰的变量一样,即含有隐性的final
定义。
当尝试再后面修改num
的值,编译器会提示错误信息,如示例6
示例6:
private void test(){
int num = 2;
num = 3;
// 编译器提示 引用外层局部变量,外层局部变量必须为final或者等同于final
// Variable used in lambda expression should be final or effectively final
MyFunctionalInterface functionLambda = x -> System.out.println(x * num);
functionLambda.function(1);
}
之所以引用外层变量必须是
final
是因为,局部变量被定义再栈帧中,java
访问局部变量的时候实际上是访问他的副本,多个线程对应了多个栈,当lambda
表达式执行线程与定义线程不是同一个情况下,如果变量不被定义为final
,当一个线程改变了变量值,另一个有可能会读取不到最新值导致出错。
java8+中的函数式接口
java8
及更高版本内置了一系列的函数式接口,被放在java.util.function
包下,用于支持j函数式编程。另外在之前的版本中存在的java.lang.Runnable
、java.util.Comparator
也都可以用于函数式编程