Hi,你的Lambda表达式掉了
写在之前
用了一段时间的Lambda 表达式,但是感觉都还是有点不系统,每次都是想到要用再去找。这里做一个总结,希望对大家能有所帮助。
内容结构
基本内容如下:
- Lambda 是什么
- Lambda 表达式实现原理
- Lambda 表达式语法格式
1 Lambda 是什么
基本概念
Lambda的基本语法格式;
//parameters -> expression
(int num) -> {return num++;}
Lambda表达式作为Java8的重要新特性,第一次出现是2014年了。但是因为技术更新的延迟和项目的历史原因,真正完全使用这个新特性还有一定的时间滞后。
那么首先来看看Java8为什么要引入Lambda的新特性呢?
Lambda是函数式编程的 λ演算的产物,百度一下,Lambda这个单词的解释是匿名函数(并不是闭包)。在Java中一切都是对象,那么为什么要引入这个匿名函数呢?这还要从函数式编程开始说起。
函数式编程
我们日常在Java中都是面向对象编程,这种叫做命令式编程,也就是一次方法调用是给对象发出一条指令,告诉对象该执行什么操作了。
而函数式编程的世界里,一切都是数学函数。函数式编程是通过某种语法或者调用函数进行编程。所以函数式编程是告诉你该做什么,而命令式编程告你该怎么做。这样的区别引发的结果就是函数式编程更能体现出逻辑含义,而且代码更加简洁,这也就是使用函数式编程的原因之一。
在命令式编程到函数式编程的转变过程中,除了语法上的变化,更重要的是思考方式的转变。
2 Lambda表达式实现原理
首先声明的是Jvm对Lambda表达式是无感知的,因为Lambda表达式作为一种语法糖,在类编译阶段已经将Lambda的语法糖编译为实际的实现结果,没有匿名函数,没有Lambda表达式,对于Jvm来说只知道这是一堆可以执行的字节码。
匿名内部类
在Java中提到Lambda表达式,一定再提到另外一个概念,匿名内部类。
@Test
public void runnableTest() {
//普通语法
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("我是普通语法线程");
}
};
runnable.run();
System.out.println("**************************");
}
上面这段代码使用匿名内部类的方式定义了一个执行线程,在编译阶段Jvm会创建一个类文件,所以匿名内部类的定义虽然和一般的类不同,但是本质没有区别。
下面是通过Lambda表达式定义一个执行线程,可以对比一下和匿名内部类的区别。
@Test
public void runnableTest() {
//Lambda语法线程
Runnable runnable1 = () -> System.out.println("我是Lambda语法线程");
runnable1.run();
}
虽然看上去类似,但是Lambda的底层实现原理并不是使用匿名内部类。Scala之前版本是使用匿名内部类来实现Lambda表达式,但是在后续版本更新中,也是通过匿名函数的方式来实现了。
为什么不是使用匿名内部类
首先,对于匿名内部类,编译器首先会创建一个类文件,如果匿名内部类很多,那么再类加载过程中就会对这些文件进行验证,导致影响应用的启动性能。加载类文件也是一个耗时过程,涉及到jar文件的解压、磁盘的IO,这些也会影响加载性能。
而且,如果Lambda表达式使用匿名内部类实现,那么每个Lambda表达式都会编译成一个类文件,这样无论是类文件加载到元空间,还是创建类的对象,都会占用内存。
同时,如果使用匿名内部类实现,那么Lambda表达式会按照匿名内部类的字节码机制进行编译,这样以后如果对Lambda表达式优化的话,那么是相当于要在匿名内部类的限制下了。
所以Lambda实际使用invokedynamic字节码指令来生成Lambda字节码。首先生成一个Lambda工厂,当实际调用时返回Lambda表达式转化成的函数式接口实例。
将Lambda表达式的方法体转换成方法供invokeddynamic指令调用。
3 Lambda 语法格式
函数式接口
Java中定义了很多函数式接口,特点就是接口中只有一个方法。
- Runnable
- Consumer
- Function
- Predicate
- Supplier
还有其他的函数式接口,同时也可以自定义函数式接口,Java提供了一个@FunctionalInterface注解,通过这个注解可以帮助验证定义的接口是否是函数式接口,如果不符合规则,编译阶段就会报错。
@FunctionalInterface
public interface LambdaInterface {
void invoke();
//如果再有这个定义,编译报错
//void anotherInvoke();
}
Lambda表达式只支持函数式接口,这一点需要注意,下面通过实现以上函数式接口,说明一下Lambda的基本语法格式。
语法格式一:无参数,无返回值
@Test
public void lambdaFormatTest1() {
//普通语法
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("我是普通语法线程");
}
};
runnable.run();
System.out.println("**************************");
//语法格式一:无参数,无返回值
Runnable runnable1 = () -> System.out.println("Lambda表达式 语法格式一");
runnable1.run();
}
语法格式二:一个参数,无返回值
@Test
public void lambdaFormatTest2() {
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
consumer.accept("Consumer 原始语法格式");
System.out.println("**************************");
//语法格式二:一个参数,无返回值
Consumer<String> consumer1 = (String str) -> System.out.println(str);
consumer1.accept("Lambda表达式 语法格式二");
System.out.println("**************************");
}
语法格式三:通过类型推断,省略参数类型
@Test
public void lambdaFormatTest2() {
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
consumer.accept("Consumer 原始语法格式");
System.out.println("**************************");
//语法格式三:通过类型推断,省略参数类型
Consumer<String> consumer2 = (str) -> System.out.println(str);
consumer2.accept("Lambda表达式 语法格式三");
}
语法格式四:一个参数省略
@Test
public void lambdaFormatTest2() {
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
consumer.accept("Consumer 原始语法格式");
System.out.println("**************************");
//语法格式四:一个参数,省略()
Consumer<String> consumer3 = str -> System.out.println(str);
consumer3.accept("Lambda表达式 语法格式四");
}
语法格式五:两个或以上参数,多条执行语句,有返回值
@Test
public void lambdaFormatTest3() {
//普通语法
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
System.out.println(comparator.compare(1, 2));
System.out.println("**************************");
//语法格式五:两个或以上参数,多条执行语句,有返回值
Comparator<Integer> comparator1 = (Integer o1, Integer o2) -> {
System.out.println("第一个参数:" + o1);
System.out.println("第二个参数:" + o2);
return o1.compareTo(o2);
};
System.out.println("Lambda表达式 语法格式五: " + comparator1.compare(1, 2));
}
语法格式六:一条执行语句,有返回值
@Test
public void lambdaFormatTest3() {
//普通语法
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
System.out.println(comparator.compare(1, 2));
System.out.println("**************************");
//语法格式六:一条执行语句,有返回值
Comparator<Integer> comparator2 = (o1, o2) -> o1.compareTo(o2);
System.out.println("Lambda表达式 语法格式六: " + comparator2.compare(1, 2));
}
以上介绍了Lambda的6中语法格式,实际使用中,Lamba经常和Stream流结合使用,变化可能更丰富些,但基本也在这6中情况中。
最后
本篇主要介绍了Lambda表达式是什么,实现原理,基本使用。其实还有很多方面没有说明,例如异常处理、并发处理等。后续还会对Lambda相关的内容继续学习,补充内容。
而且Lambda和Stream的结合是日常开发中非常高频的使用场景,Stream也是Java8的亮瞎眼新特性之一,接下来会总结一下Stream的相关内容。