Lambda表达式和Stream流
Java8新引入了Lambda表达式在我们工作和学习中肯定或多或少会遇到。Lambda表达式是一种用于取代匿名类,把函数行为表述为函数式编程风格的一种匿名函数,Lambda表达式的执行结果是函数式接口的一个匿名对象。使用Lambda+Stream表达式优化代码,让你的代码更加优雅。
本文关键字:Lambda表达式语法详解,底层实现原理,Lambda和匿名内部类的对比,StreamAPI,Stream常用方法
一:Lambda表达式
相信Lambda表达式或多或少都会了解,在这闲言少叙用一个简单的例子引入:
示例:
创建一个新的线程,用抽象方法run方法输出一句话。
public static void main(String[] args) {
// 开启一个新的线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程中执行的代码 : "+Thread.currentThread().getName());
}
}).start();
System.out.println("主线程中的代码:" + Thread.currentThread().getName());
}
分析:
- Thread类需要一个Runnable接口作为参数,其中的抽象方法run方法是用来指定线程任务内容的核心
- 为了指定run方法体,不得不需要Runnable的实现类
- 为了省去定义一个Runnable 的实现类,不得不使用匿名内部类
- 必须覆盖重写抽象的run方法,所有的方法名称,方法参数,方法返回值不得不都重写一遍,而且不能出错
- 而实际上,我们只在乎方法体中的代码
下面用Lambda表达式去写:
Lambda表达式是一个匿名函数,可以理解为一段可以传递的代码
new Thread(() -> {
System.out.println("新线程Lambda表达式..." +Thread.currentThread().getName()); })
.start();
Lambda表达式的优点:简化了匿名内部类的使用,语法更加简单。
匿名内部类语法冗余,可以发现Lambda表达式是简化匿名内部类的一种方式。他们的区别在下文慢慢揭露。
语法规则
Lambda省去了面向对象的条条框框,Lambda的标准格式由3个部分组成:
(参数类型 参数名称) -> {
代码体;
}
格式说明:
- (参数类型 参数名称):参数列表
- {代码体;} :方法体
- -> : 箭头,分割参数列表和方法体
在lambda表达式的标准写法基础上,可以使用省略写法的规则为:
- 小括号内的参数类型可以省略
- 如果小括号内有且仅有一个参数,则小括号可以省略
- 如果大括号内有且仅有一个语句,可以同时省略大括号,return 关键字及语句分号。
使用前提
Lambda表达式的语法是非常简洁的,但是Lambda表达式在使用时有几个条件要特别注意
- 方法的参数或局部变量类型必须为接口才能使用Lambda
- 接口中有且仅有一个抽象方法(@FunctionalInterface)
Lambda表达式原理
我们知道匿名内部类的本质是在编译时生成一个Class 文件,比如说下面这段代码:
public class Demo01Lambda {
public static void main(String[] args) {
// 开启一个新的线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程中执行的代码 : "+Thread.currentThread().getName());
}
}).start();
System.out.println("主线程中的代码:" + Thread.currentThread().getName());
System.out.println("---------------");
/*new Thread(() -> { System.out.println("新线程Lambda表达式..." +Thread.currentThread().getName()); })
.start();*/
}
}
在编译时会生成Demo01Lambda1.class文件
还可以通过反编译工具来查看生成的代码 XJad 工具来查看,结果如下:
static class Demo01Lambda$1
implements Runnable
{
public void run()
{
System.out.println((new StringBuilder()).append("新线程中执行的代码 : " ).append(Thread.currentThread().getName()).toString());
}
Demo01Lambda$1()
{
}
}
那么Lambda表达式的原理是什么?当我用反编译工具查看时并不支持,于是使用JDK自带的工具javap对字节码进行反汇编。
javap -c -p 文件名.class
-c:表示对代码进行反汇编
-p:显示所有的类和成员
反汇编的结果:
E:\workspace\OpenClassWorkSpace\JDK8Demo\target\classes\com\test\jdk\lambda>javap -c -p Demo03Lambda.class
Compiled from "Demo03Lambda.java"
public class com.bobo.jdk.lambda.Demo03Lambda {
public com.bobo.jdk.lambda.Demo03Lambda();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokedynamic #2, 0 // InvokeDynamic #0:show:()Lcom/bobo/jdk/lambda/service/UserService;
5: invokestatic #3 // Method goShow:(Lcom/bobo/jdk/lambda/service/UserService;)V
8: return
public static void goShow(com.bobo.jdk.lambda.service.UserService);
Code:
0: aload_0
1: invokeinterface #4, 1 // InterfaceMethod com/bobo/jdk/lambda/service/UserService.show:()V
6: return
private static void lambda$main$0();
Code:
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String Lambda show 方法执行了...
5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
有没有感觉看不懂,其实配合注释,可以关注到最后发现了一个静态方法: lambda$main$0();方法体中的代码就是在这个里面执行。为了探求深层次的实现,可以在运行时添加-Djdk.internal.lambda.dumpProxyClasses, 加上这个参数会将内部class码输出到一个文件中:
命令执行
E:\workspace\OpenClassWorkSpace\JDK8Demo\target\classes>java -Djdk.internal.lambda.dumpProxyClasses com.bobo.jdk.lambda.Demo03Lambda
Lambda show 方法执行了...
进行反编译后的内容:
public class Demo03Lambda {
public static void main(String[] args) {
goShow(new UserService() {
@Override
public void show() {
Demo03Lambda.lambda$main$0();
}
});
System.out.println("----------");
}
public static void goShow(UserService userService){
userService.show();
}
private static void lambda$main$0();
System.out.println("Lambda show 方法执行了...");
}
}
可以看到这个匿名的内部类实现了UserService接口,并重写了show()方法。在show方法中调用了Demo03Lambda.lambda$main$0(),也就是调用了Lambda中的内容。
Lambda表达式和匿名内部类的区别
匿名内部类在编译的时候会产生一个class文件。
Lambda表达式在程序运行的时候会形成一个类。
- 在类中新增了一个方法,这个方法的方法体就是Lambda表达式中的代码
- 还会形成一个匿名内部类,实现接口,重写抽象方法
- 在接口中重写方法会调用新生成的方法
二:函数式接口
介绍
使用Lambda表达式的前提是需要有函数式接口,而Lambda表达式使用时不关心接口名,抽象方法名。只关心抽象方法的参数列表和返回值类型。下面的代码是函数式接口的一个简单展示,后文会简单介绍一些JDK提供的常用的函数式接口。
public class Demo01Fun {
public static void main(String[] args) {
fun1((arr)->{
int sum = 0 ;
for (int i : arr) {
sum += i;
}
return sum;
});
}
public static void fun1(Operator operator){
int[] arr = {
1,2,3,4};
int sum = operator.getSum(arr);
System.out.println("sum = " + sum);
}
}
/**
* 函数式接口
*/
@FunctionalInterface
interface Operator{
int getSum(int[] arr);
}
JDK中提供的一些常用的函数式接口
在 java.util.function 包中
Supplier
无参有返回值的接口,对于的Lambda表达式需要提供一个返回数据的类型。
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
使用:
/**
* Supplier 函数式接口的使用
*/
public class SupplierTest {
public static void main(String[] args) {
fun1(()->{
int arr[] = {
22,33,55,66,44,99,10};
// 计算出数组中的最大值
Arrays.sort(arr);
return arr[arr.length-1];
});
}
private static void fun1(Supplier<Integer> supplier){
// get() 是一个无参的有返回值的 抽象方法
Integer max