Java8新特性
Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。
Java8 新增了非常多的特性,我们主要讨论以下几个:
- Lambda 表达式:Lambda 允许把函数作为参数传递到方法中。
- 方法引用:可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- 默认方法 :默认方法就是一个在接口里面有了一个实现的方法。
- 新工具:新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
- Stream API:新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
- Date Time API:加强对日期与时间的处理。
- Optional 类:Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Nashorn, JavaScript 引擎: Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
Java 8与 Java 7 的编程风格是有区别的,如下例子:
📖Lambda 表达式(重要)
✅匿名内部类存在的问题
当需要启动一个线程去完成任务时,通常会通过一个 Runnable 接口的匿名内部类对象来指定线程要执行的任务内容,并使用 Thread 类来启动该线程。
public class test01 {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
System.out.println("新线程任务执行!");
}
});
}
}
代码分析:
上面创建的匿名内部类的过程做了哪些事情:
- 定义了一个没有名字的类
- 这个类实现了Runnable接口
- 创建了这个类的对象
而使用匿名内部类语法是很冗余的。
Lambda体验:
new Thread(() -> System.out.println("新线程2任务执行!")).start();
使用Lambda表达式可以发现代码更加简单了。
Lambda的优点:
所以Lambda的优点就是简化匿名内部类的使用,语法更加简单。
✅Lambda的标准格式
Lambda省去面向对象的条条框框,Lambda的标准格式格式由3个部分组成:
(参数类型 参数名称) -> {
代码体;
}
格式说明:
- (参数类型 参数名称):参数列表
- {代码体;}:方法体
->
:箭头,分隔参数列表和方法体
Lambda与方法的对比:
匿名内部类
public void run() {
System.out.println("aa");
}
Lambda
() -> System.out.println("bb!")
✅Lambda的前提条件
Lambda的语法非常简洁,但是Lambda表达式不是随便使用的,使用时有2个前提条件::
- 方法的参数或局部变量类型必须为接口才能使用Lambda。
- 接口中有且仅有一个抽象方法。
✅Lambda的重要特征
- 简洁性
Lambda 表达式提供了一种更为简洁的语法,尤其适用于函数式接口。相比于传统的匿名内部类,Lambda 表达式使得代码更为紧凑,减少了样板代码的编写。
比如将下面集合进行排序:
//有如下集合,要求进行排序
List<String> names = new ArrayList<String>();
names1.add("Google ");
names1.add("Runoob ");
names1.add("Taobao ");
names1.add("Baidu ");
names1.add("Sina ");
//Java7实现
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
//Java8新特性Lambda实现
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
非常明显的看出Lambda表达式的简洁性。
- 函数式编程支持
Lambda 表达式是函数式编程的一种体现,它允许将函数当作参数传递给方法,或者将函数作为返回值,这种支持使得 Java 在函数式编程方面更为灵活,能够更好地处理集合操作、并行计算等任务。
// 使用 Lambda 表达式作为参数传递给方法
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));
- 变量捕获
Lambda 表达式可以访问外部作用域的变量,这种特性称为变量捕获,Lambda 表达式可以隐式地捕获 final 或事实上是 final 的局部变量。
// 变量捕获
int x = 10;
MyFunction myFunction = y -> System.out.println(x + y);
myFunction.doSomething(5); // 输出 15
- 方法引用
Lambda 表达式可以通过方法引用进一步简化,方法引用允许你直接引用现有类或对象的方法,而不用编写冗余的代码。
// 使用方法引用
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(System.out::println);
- 可并行性
// 使用 Lambda 表达式和 Stream API 进行并行计算
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.parallelStream().mapToInt(Integer::intValue).sum();
✅Lambda省略格式
在Lambda标准格式的基础上,使用省略写法的规则为:
- 小括号内参数的类型可以省略
- 如果小括号内有且仅有一个参数,则小括号可以省略
- 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号
//省略前
(int a) -> {
return new Person();
}
//省略后
a -> new Person()
Lambda 表达式实例:
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
✅了解Lambda的实现原理
先说结论:
Lambda在程序运行的时候:
- 在类中新增一个静态私有的方法,这个方法的方法体就是Lambda表达式中的代码
- 还会形成一个匿名内部类,实现接口,重写抽象方法,并在重写方法中会调用新生成的静态私有方法
代码分析:
匿名内部类情况:
- 准备下面匿名内部类代码:
//接口
public interface Animal {
public abstract void eat();
}
//在test02类中调用
public class test02 {
public static void main(String[] args) {
goOpen(new Animal() {
@Override
public void eat() {
System.out.println("使用匿名内部类实现吃东西");
}
});
}
public static void goOpen(Animal open){
open.eat();
}
}
- 将代码进行编译,产生了下面文件
- 将生成的匿名类使用jadx反编译工具进行反编译,得到的代码如下:
package com.jdk8;
/* loaded from: test02$1.class */
class test02$1 implements Animal {
test02$1() {}
public void eat() {
System.out.println("使用匿名内部类实现吃东西");
}
}
发现生成的test02$1类实现了Animal接口,并重写了 eat方法。
Lambda情况:
- 改写成Lambda代码:
public class test02 {
public static void main(String[] args) {
goOpen(() -> System.out.println("使用Lambda实现吃东西"));
}
public static void goOpen(Animal open){
open.eat();
}
}
- 将代码进行编译,发现并没有新增匿名class字节文件
- 使用JDK自带的
javap
工具对test02.class
字节码进行反汇编,命令如下:
javap -c -p 文件名.class
-c:表示对代码进行反汇编
-p:显示所有的类和成员
结果如下:发现反汇编后的代码新生成了一个序号为0的方法lambda$main$0
,而这个方法里面的内容就是Lambda代码块中的内容。
那么这个新生成的方法是如何调用的呢?我们可以使用下面指令在运行时会将生成的内部类class码输出到一个文件中。
java -Djdk.internal.lambda.dumpProxyClasses 要运行的包名.类名
- 将这个在运行时生成的匿名内部类进行反编译,代码如下
我们发现运行时生成的匿名内部类中会去调用新生成的方法,而新生成的lambda$main$0
方法中的内容正是Lambda中的代码。
所以Lambda的实现原理就是开头所说的:
Lambda在程序运行的时候:
- 在类中新增一个静态私有的方法,这个方法的方法体就是Lambda表达式中的代码
- 还会形成一个匿名内部类,实现接口,重写抽象方法,并在重写方法中会调用新生成的静态私有方法
✅Lambda和匿名内部类对比
了解Lambda和匿名内部类在使用上的区别
所需的类型不一样、
- 匿名内部类,需要的类型可以是类,抽象类,接口
- Lambda表达式,需要的类型必须是接口
抽象方法的数量不一样
匿名内部类所需的接口中抽象方法的数量随意
Lambda表达式所需的接口只能有一个抽象方法
实现原理不同
- 匿名内部类是在编译后会形成class
- Lambda表达式是在程序运行的时动态生成class
📖方法引用
✅方法引用的格式
使用双冒号::
写法,被称为方法引用。
- 符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
- 应用场景 : 如果Lambda所要实现的方案 , 已经有其他方法存在相同方案,那么则可以使用方法引用。
常见引用方式
方法引用在JDK 8中使用方式相当灵活,有以下几种形式:
instanceName::methodName
对象::方法名ClassName::staticMethodName
类名::静态方法ClassName::methodName
类名::普通方法ClassName::new
类名::new 调用的构造器TypeName[]::new
String[]::new 调用数组的构造器
✅构造器引用
✅静态方法引用
📖函数式接口
✅常用内置函数式接口
- Supplier接口
- Consumer接口
- Function接口
- Predicate接口
应用场景:
- Supplier 接口:
- 用于延迟计算,只有在需要时才进行计算,比如惰性加载。
- 用于生成随机数、UUID 或其他对象。
- 用于配置信息的提供。
- Consumer 接口:
- 用于消费某个参数,比如打印日志、发送消息等。
- 用于处理集合中的每个元素,如列表的遍历。
- Function 接口:
- 用于数据的转换,如将字符串转换为整数、将对象转换为字符串等。
- 用于数据的处理,如格式化数据、映射数据等。
- 用于流式处理中的 map 操作,将流中的元素映射为另一种类型的元素。
- Predicate 接口:
- 用于条件判断,如筛选符合条件的元素。
- 用于过滤集合中的元素,如筛选出满足某些条件的对象。
- 用于流式处理中的 filter 操作,根据条件过滤流中的元素。
这些函数式接口提供了一种灵活的方式来处理函数,可以使代码更加简洁、易读和易于维护。它们在 Java 8 引入的流式处理、并行处理等新特性中得到了广泛的应用,使得对数据的处理更加高效和方便。同时,它们也为函数式编程和Lambda表达式的使用提供了重要的支持