Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。使用 Lambda 表达式可以将代码块作为方法参数,使代码变的更加简洁紧凑。坦白的说,初次看见Lambda表达式瞬间头就大了,为了更好的理解,我们可以把Lambda 表达式当作是一种匿名函数(对 Java 而言这并不完全正确,但现在姑且这么认为),简单地说,就是没有声明的方法,即没有访问修饰符、返回值声明和名字。
Lambda 表达式的结构
Java 中的 Lambda 表达式通常使用 (argument) -> (body) 语法书写,例如:
(arg1, arg2...) -> expression
(arg1, arg2...) -> { body }
(type1 arg1, type2 arg2...) -> { body }
Lambda 表达式的结构说明:
- 一个 Lambda 表达式可以有零个或多个参数
- 参数的类型既可以明确声明,也可以根据上下文来推断。例如:(int a)与(a)效果相同
- 所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)
- 空圆括号代表参数集为空。例如:() -> 42
- 当只有一个参数,且其类型可推导时,圆括号()可省略。例如:a -> return a*a
- Lambda 表达式的主体可包含零条或多条语句
- 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致
- 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空
列举几个 Lambda 表达式的例子:
java
(int a, int b) -> { return a + b; }
() -> System.out.println("Hello World");
(String s) -> { System.out.println(s); }
() -> { return 3.1415 };
Lambda表达式可能会有返回值,编译器会根据上下文推断返回值的类型。如果lambda的语句块只有一行,不需要return关键字。下面两个写法是等价的:
java
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
和
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );
Lambda 实战
下面我们看看Lambda在开发中的实际使用
替代匿名内部类
1.Runnable 接口
java
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("The runable now is using!");
}
}).start();
//用lambda
new Thread(() -> System.out.println("It's a lambda function!")).start();
2.Comparator 接口
java
List names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
在 Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8 提供了更简洁的语法,lambda 表达式:
java
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
可以看出,代码变得更短且更具有可读性,但是实际上还可以写得更短:
java
Collections.sort(names, (String a, String b) -> b.compareTo(a));
对于函数体只有一行代码的,我们还可以去掉大括号{}以及 return 关键字,但是我们还可以写得更短点:
java
names.sort((a, b) -> b.compareTo(a));
List 类本身就有一个sort 方法。并且 Java 编译器可以自动推导出参数类型,所以你可以不用再写一次类型。
3.自定义接口
通过上面的例子可以看出Lambda语法只保留实际用到的代码,把无用代码全部省略。那它对接口有没有要求呢?我们发现这些匿名内部类只重写了接口的一个方法,当然也只有一个方法须要重写。这就是我们上文提到的函数式接口,也就是说只要方法的参数是函数式接口都可以用 Lambda 表达式。
java
@FunctionalInterface
public interface Comparator<T>{}
@FunctionalInterface
public interface Runnable{}
我们自定义一个函数式接口
java
@FunctionalInterface
public interface Comparator<T>{}
@FunctionalInterface
public interface Runnable{}
我们自定义一个函数式接口
@FunctionalInterface
public interface LambdaInterface {
void f();
}
//使用
public class LambdaClass {
public static void forEg() {
lambdaInterfaceDemo(()-> System.out.println("自定义函数式接口"));
}
//函数式接口参数
static void lambdaInterfaceDemo(LambdaInterface i){
i.f();
}
}
集合迭代
要遍历数组中的所有元素,通常使用for循环的方法,而使用 Lambda 表达式的方法不止一种。在下面的例子中,我们先是用常用的箭头语法创建 Lambda 表达式,之后,使用 Java 8 全新的双冒号(::)操作符将一个常规方法转化为 Lambda 表达式:
java
//Java 8之前:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
System.out.println(n);
}
//Java 8之后:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// 使用Java 8的方法引用更方便,方法引用由::双冒号操作符标示,
// 看起来像C++的作用域解析运算符
list.forEach(System.out::println);
方法的引用
Java 8 允许使用 :: 关键字来传递方法或者构造函数引用,无论如何,表达式返回的类型必须是 functional-interface。
1、引用构造器方法:
语法:
java
<函数式接口> <变量名> = <类>::<new>
//调用
<变量名>.接口方法([实际参数...])
把方法的所有参数全部传递给引用的构造器,请注意构造方法没有参数,根据参数类型自动推断调用的构造器方法;
示例代码:
java
public interface LambdaTest5 {
abstract String creatString(char[] c);
}
public class Main {
public static void main(String[] args) {
LambdaTest5 lt5 = String::new;
System.out.println(lt5.creatString(new char[]{'1','2','3','a'}));
}
}
2、引用类静态方法:
语法:
java
<函数式接口> <变量名> = <类>::<类方法名称>
//调用
<变量名>.接口方法([实际参数...])
将调用方法时的传递的实际参数,全部传递给引用的方法,执行引用的方法;
示例代码:
public interface LambdaTest3 { abstract void sort(List<Integer> list,Comparator<Integer> c); } public class Main { public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); list.add(50); list.add(18); list.add(6); System.out.println(list.toString()+"排序之前"); LambdaTest3 lt3 = Collections::sort; lt3.sort(list, (a,b) -> { return a-b; }); System.out.println(list.toString()+"排序之后"); } }
3、引用实例方法:
语法:
java
<函数式接口> <变量名> = <实例>::<实例方法名>
//调用
<变量名>.接口方法([实际参数...])
将调用方法时的传递的实际参数,全部传递给引用的方法,执行引用的方法;
示例代码:
java
public class LambdaClassTest {
public int add(int a, int b){
System.out.println("LambdaClassTest类的add方法");
return a+b;
}
}
public class Main {
public static void main(String[] args) {
LambdaClassTest lct = new LambdaClassTest();
System.out.println(lct.add( 5, 8));
}
}
4、引用类的实例方法:
定义、调用接口时,需要多传递一个参数,并且参数的类型与引用实例的类型一致
语法:
java
//定义接口
interface <函数式接口>{
<返回值> <方法名>(<类><类名称>,[其他参数...]);
}
<函数式接口> <变量名> = <类>::<类实例方法名>
//调用
<变量名>.接口方法(类的实例,[实际参数...])
将调用方法时的传递的实际参数,从第二个参数开始(第一个参数指定的类的实例),全部传递给引用的方法,执行引用的方法;
示例代码:
java
public class LambdaClassTest {
public int add(int a, int b){
System.out.println("LambdaClassTest类的add方法");
return a+b;
}
}
public interface LambdaTest4 {
abstract int add(LambdaClassTest lt,int a,int b);
}
public class Main {
public static void main(String[] args) {
LambdaTest4 lt4 = LambdaClassTest::add;
LambdaClassTest lct = new LambdaClassTest();
System.out.println(lt4.add(lct, 5, 8));
}
}
Lambda表达式对比匿名类
使用匿名类与 Lambda 表达式的一大区别在于关键词的使用。匿名类的 this 关键字指向匿名类,而lambda表达式的 this 关键字指向写就 Lambda 的外部类。另一个不同点是二者的编译方式。Java编译器将lambda表达式编译成类的私有方法。使用了Java 7的 invoke dynamic 字节码指令来动态绑定这个方法。
Lambda 作用域
在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。若是局部变量没有加final关键字,系统会自动添加,此后再修改该局部变量,会报错;
参考:
http://blog.oneapm.com/apm-tech/226.html
http://www.importnew.com/16436.html
https://blog.csdn.net/u010412719/article/details/53493226
Lambda系列教材 (一)- Java Lambda 表达式教程
https://www.cnblogs.com/liuxiaozhi23/p/10880147.html