文章目录
- 新特性:
- 1.模块化系统
- 使用模块化
- module-info.java:
- exports:
- opens:
- requires:
- provides:
- uses:
- 2.JShell
- 启动Jshell
- 执行计算
- 定义变量
- 定义方法
- 定义类
- 帮助命令
- 查看定义的变量:/vars
- 查看定义的方法:/methods
- 查看定义的类:/types
- 列出输入源条目:/list
- 编辑源条目:/edit
- 删除源条目:/drop
- 保存文件:/save
- 打开文件:/open
- 重置jshell:/reset
- 查看引入的包:/imports
- 退出jshell:/exit
- 3.集合工厂方法
- 4.接口的改变
- 5.Stream API
- ofNullable
- iterate
- takeWhile
- dropWhile
- 6.Optional新增方法
- 7.Deprecated注解
- 8.钻石操作符<>
- 9.异常处理
- 10.String底层变更
- 11.不能使用下划线作为变量名
- 12.try-with-resources 优化
- 13.CompletableFuture增强
- 14.垃圾收集器
新特性:
1.模块化系统
模块化系统是 Java9 架构的一次重大变革,它旨在解决长期以来 Java 应用所面临的一些结构性问题,特别是在大型系统和微服务架构中。
如果把 Java 8 比作单体应用,那么引入模块系统之后,从 Java 9 开始,Java 就华丽的转身为微服务。模块系统,项目代号 Jigsaw,最早于 2008 年 8 月提出,2014 年跟随 Java 9 正式进入开发阶段,最终跟随 Java 9 发布于 2017 年 9 月。
那么什么是模块系统?官方的定义是A uniquely named, reusable group of related packages, as well as resources (such as images and XML files) and a module descriptor。模块的载体是 jar 文件,一个模块就是一个 jar 文件,但相比于传统的 jar 文件,模块的根目录下多了一个 module-info.class 文件,也即 module descriptor。 module descriptor 包含以下信息:
- 模块名称
- 依赖哪些模块
- 导出模块内的哪些包(允许直接 import 使用)
- 开放模块内的哪些包(允许通过 Java 反射访问)
- 提供哪些服务
- 依赖哪些服务
我们知道在 Java 中, Java 文件是最小的可执行文件,为了更好地管理这些 Java 文件,我们需要用 package 将同一类的 Java 文件统一管理起来,多个 package 文件、Java 文件可以打包成一个 jar 文件,现在 Java 9 在 package 上面增加 module,一个 module 可以包含多个 package,所以从代码结构上来看层级关系是这样的:jar > module > package > java 文件。
引入模块化其实在一定程度上增加编码的复杂度,特别是对于之前习惯于传统的包结构的开发者来说。然而,引入模块化有着一些非常重要的优势,这些优势可以帮助我们更好地组织、管理和维护我们的Java应用程序,提高开发效率和代码质量。
- 更好的代码组织:模块化允许开发者将代码划分为独立的模块,每个模块都可以作为一个功能单元进行开发和测试。这有助于编写高内聚低耦合的代码,并清晰地管理模块间的依赖关系。
- 提升性能与可伸缩性:通过模块化,JDK 和 JRE 可以重新安排到可互操作的模块中,支持创建可在小型设备上执行的可扩展运行时。模块化还使得 Java 应用更容易适配到更小的设备中,这对于嵌入式系统和物联网设备尤为重要。
- 改善安全和维护性:模块化 JDK 和 JRE 可以提高安全性、维护性,并允许定制运行时环境。例如,如果某个网络应用不需要 Swing 图形库,可以在打包应用时选择不包含该库,从而减少性能消耗。
- 提高编译效率:在 Java 9 中,构建系统通过 JEP 201 进行编译和实施模块边界,增强了在构建时编译模块和识别模块边界的能力。
- 促进大型项目管理:对于大型和复杂的应用程序,模块化可以将应用分解为完成特定功能的小块,这有助于简化开发过程和管理依赖关系。
- 支持运行时组合:模块化允许在运行时动态组合不同的模块,提供了更大的灵活性和可定制性。
- 便于迁移和维护:模块化可以帮助开发者逐步迁移旧的非模块化代码库至新的模块化结构,而不必一开始就全面转换整个代码库。
模块化怎么体现的呢?下图是Java 8与Java 9的目录结构:
可以看出 Java 9 中没有jre,没有rt.jar,没有tools.jar,而是多了一个 jmods,该文件夹下都是一个一个的模块。
在Java 9之前的项目中,一个简单的"hello world"程序,也需要引入rt.jar文件,导致生成的jar包比较庞大。然而,Java 9引入了模块化的概念,使得情况有了改变。现在,使用Java 9及更高版本,你只需要引入程序所依赖的模块,而不是整个rt.jar,这使得构建简单程序时所生成的jar包大小大大减小。这种模块化的方式使得Java应用程序更加轻量化、灵活,也更容易管理和维护。
使用模块化
创建两个模块:testA、testB
testA:com.linging.config、com.linging.model、module-info.java
testB:Main、module-info.java
public class User {private String name;private String age;//getter and setter...@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age='" + age + '\'' +'}';}private void hello(){System.out.println("访问private方法");}protected void hello2(){System.out.println("访问protected方法");}public void hello3(){System.out.println("访问public方法");}
}
module-info.java:
module-info.java 文件是 Java 模块系统的核心组成部分,用于定义 Java 模块的信息
。从 JDK 9 开始,这个文件被用来描述模块的元数据,包括模块名称、它导出的包、它打开的包以及其他模块依赖等信息
。
module <模块名> {// 模块声明,例如导出包、打开包、依赖其他模块等
}
exports:
exports
用于在编译时和运行时允许其他模块访问指定包中的public
成员,不能访问private
、protected
或包私有的成员。
exports <包名>;
module testA {exports com.linging.model;
}
opens:
opens
用于在运行时允许其他模块通过反射
访问指定包中的所有类型及其成员,包括private
和protected
成员。
//对所有模块开放
opens <包名>;
//对指定模块开放
opens <包名> to <模块1>, <模块2>...;
module testA {exports com.linging.model;opens com.linging.model;
}
module testA {exports com.linging.model;opens com.linging.model to testB;
}
User user = new User();
Class<? extends User> aClass = user.getClass();// private方法
Method hello = aClass.getDeclaredMethod("hello");
hello.setAccessible(true);
hello.invoke(user);// protected方法
Method hello2 = aClass.getDeclaredMethod("hello2");
hello2.setAccessible(true);
hello2.invoke(user);//public方法
Method hello3 = aClass.getDeclaredMethod("hello3");
hello3.invoke(user);
requires:
requires
关键字用于声明一个模块对其它模块的依赖关系。这意味着当前模块需要使用到另一个模块所提供的类或服务。在 module-info.java 文件中,requires 子句用于指定这些依赖关系。
//在编译时和运行时都需要该模块
requires <模块名>;
//静态依赖:表示该模块在编译时是必需的,但在运行时是可选的。也就是说,如果在运行时该模块不存在,程序仍然可以启动,只要不执行依赖于该模块的代码
requires static <模块名>;
//传递性依赖:使用transitive关键字表示该模块的依赖是传递的。这意味着如果模块A依赖于模块B,并且模块B使用了requires transitive声明依赖于模块C,那么模块A也将隐式地依赖于模块C
requires transitive <模块名>;
module testB {requires testA;
}
provides:
provides
关键字用于声明模块提供的服务实现。它与uses
关键字一起使用,实现了服务提供者接口(SPI)机制,允许模块声明它们提供的服务实现,以便其他模块可以使用这些服务。
provides <service-interface> with <implementation1-class>,<implementation2-class>...;
module testA {exports com.linging.service;provides com.linging.service.UserServicewith com.linging.service.impl.UserServiceImpl,com.linging.service.impl.UserServiceImpl2;
}
uses:
uses
关键字用于声明模块需要的服务。具体来说,uses
用于指定模块需要使用的服务接口或抽象类。这个服务通常是一个接口或抽象类,而不是实现类。
uses <service-interface>;
module testB {requires testA;uses com.linging.service.UserService;
}
ServiceLoader<UserService> serviceLoader = ServiceLoader.load(UserService.class);
for (UserService service : serviceLoader) {System.out.println(service.getUser());
}
2.JShell
交互式编程环境是一种让程序员能够即时输入代码并立即获得反馈的开发环境。每输入一行代码,系统就会立刻执行并显示结果,使得用户可以快速验证想法、进行简单计算等操作。尽管这种环境不太适合处理复杂的工程需求,但在快速验证和简单计算等场景下非常实用。尽管其他高级编程语言(比如Python)早就拥有了交互式编程环境,Java直到Java 9才正式推出了类似的工具。
下面就来一起学习下,这个Java中的交互式编程环境Jshell。
启动Jshell
C:\Users\Linging>jshell
| 欢迎使用 JShell -- 版本 9.0.4
| 要大致了解该版本, 请键入: /help intro
jshell>
执行计算
jshell> 1+1
$1 ==> 2jshell> 2*3
$2 ==> 6
定义变量
jshell> int a = 1, b = 2;
a ==> 1
b ==> 2jshell> a+b
$7 ==> 3
定义方法
jshell> public int sum(int a, int b){...> return a + b;...> }
| 已创建 方法 sum(int,int)jshell> sum(1,2)
$9 ==> 3
定义类
jshell> public class Calc{...> int a;...> int b;...> public Calc(){}...> public Calc(int a, int b){...> this.a = a;...> this.b = b;...> }...> public int add(){...> return a + b;...> }...> }
| 已创建 类 Calcjshell> Calc c = new Calc(1,2)
c ==> Calc@69ea3742jshell> c.add()
$12 ==> 3
帮助命令
jshell> /help
| 键入 Java 语言表达式, 语句或声明。
| 或者键入以下命令之一:
| /list [<名称或 id>|-all|-start]
| 列出您键入的源
| /edit <名称或 id>
| 编辑按名称或 id 引用的源条目
| /drop <名称或 id>
| 删除按名称或 id 引用的源条目
| /save [-all|-history|-start] <文件>
| 将片段源保存到文件。
| /open <file>
| 打开文件作为源输入
| /vars [<名称或 id>|-all|-start]
| 列出已声明变量及其值
| /methods [<名称或 id>|-all|-start]
| 列出已声明方法及其签名
| /types [<名称或 id>|-all|-start]
| 列出已声明的类型
| /imports
| 列出导入的项
| /exit
| 退出 jshell
| /env [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>] ...
| 查看或更改评估上下文
| /reset [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>]...
| 重启 jshell
| /reload [-restore] [-quiet] [-class-path <路径>] [-module-path <路径>]...
| 重置和重放相关历史记录 -- 当前历史记录或上一个历史记录 (-restore)
| /history
| 您键入的内容的历史记录
| /help [<command>|<subject>]
| 获取 jshell 的相关信息
| /set editor|start|feedback|mode|prompt|truncation|format ...
| 设置 jshell 配置信息
| /? [<command>|<subject>]
| 获取 jshell 的相关信息
| /!
| 重新运行上一个片段
| /<id>
| 按 id 重新运行片段
| /-<n>
| 重新运行前面的第 n 个片段
|
| 有关详细信息, 请键入 '/help', 后跟
| 命令或主题的名称。
| 例如 '/help /list' 或 '/help intro'。主题:
|
| intro
| jshell 工具的简介
| shortcuts
| 片段和命令输入提示, 信息访问以及
| 自动代码生成的按键说明
| context
| /env /reload 和 /reset 的评估上下文选项
查看定义的变量:/vars
jshell> /vars
| int $1 = 2
| int $2 = 6
| int a = 1
| int b = 2
| int $7 = 3
| int $9 = 3
| Calc c = Calc@69ea3742
| int $12 = 3
查看定义的方法:/methods
jshell> /methods
| int sum(int,int)
查看定义的类:/types
jshell> /types
| class Calc
列出输入源条目:/list
查看jshell之前输入的所有内容:
jshell> /list1 : 1+12 : 2*35 : int a = 1, b = 2;6 : int a = 1, b = 2;7 : a+b8 : public int sum(int a, int b){return a + b;}9 : sum(1,2)10 : public class Calc{int a;int b;public Calc(){}public Calc(int a, int b){this.a = a;this.b = b;}public int add(){return a + b;}}11 : Calc c = new Calc(1,2);12 : c.add()
左侧的数字为条目id,可以利用该id,进行编辑和删除操作。
编辑源条目:/edit
jshell> /edit 1
删除源条目:/drop
jshell> /list1 : 1+12 : 2*35 : int a = 1, b = 2;6 : int a = 1, b = 2;7 : a+b8 : public int sum(int a, int b){return a + b;}9 : sum(1,2)10 : public class Calc{int a;int b;public Calc(){}public Calc(int a, int b){this.a = a;this.b = b;}public int add(){return a + b;}}11 : Calc c = new Calc(1,2);12 : c.add()13 : 1+2;14 : 1+4;15 : 1+4;jshell> /drop 1
| 已删除 变量 $1jshell> /list2 : 2*35 : int a = 1, b = 2;6 : int a = 1, b = 2;7 : a+b8 : public int sum(int a, int b){return a + b;}9 : sum(1,2)10 : public class Calc{int a;int b;public Calc(){}public Calc(int a, int b){this.a = a;this.b = b;}public int add(){return a + b;}}11 : Calc c = new Calc(1,2);12 : c.add()13 : 1+2;14 : 1+4;15 : 1+4;
保存文件:/save
// 未指定路径,则保存在启动jshell的当前目录
jshell> /save me.txt
jshell> /save C:\Users\Linging\Desktop\jmod\my.txt
打开文件:/open
// 当切换了jshell环境后,可以通过打开之前保存的文件来快速还原之前的执行内容
jshell> /open my.txtjshell> /list1 : 1+1
重置jshell:/reset
jshell> /reset
| 正在重置状态。jshell> /list
查看引入的包:/imports
jshell> /imports
| import java.io.*
| import java.math.*
| import java.net.*
| import java.nio.file.*
| import java.util.*
| import java.util.concurrent.*
| import java.util.function.*
| import java.util.prefs.*
| import java.util.regex.*
| import java.util.stream.*
退出jshell:/exit
jshell> /exit
| 再见
3.集合工厂方法
在 Java 9 之前,要构建一个不可变集合往往需要经历若干繁琐的步骤,如初始化集合、添加元素以及对其进行封装处理。Java 9 通过引入专门的不可变集合(包括List、Set、Map)特性,旨在简化这一流程,提供了一个既简洁又安全的方法来创建不可变集合,确保了集合内容的不变性和线程安全性。
其内容包括:
- List.of():创建一个不可变的 List,可以传递任意数量(与其他工具类注意区分)的元素,注意里面不能有null。
- Set.of():创建一个不可变的 Set,可以传递任意数量(与其他工具类注意区分)的元素,注意里面不能有null。
- Map.of() 和 Map.ofEntries():用于创建一个不可变的 Map。Map.of() 可以直接传递键值对,而 Map.ofEntries() 可以通过 Map.entry(k, v) 创建条目,注意key和value都能为null。 以上方法创建的集合为不可变对象,不能添加、修改、删除。
List<Integer> list = List.of(1, 2, 3, 4, 5);Set<Integer> set = Set.of(1, 2, 3, 4, 5);Map<String, String> map1 = Map.of("name", "zhangsan", "age", "18");
Map<String, String> map2 = Map.ofEntries(Map.entry("name", "zhangsan"), Map.entry("age", "18"));
List.of和Arrays.asList比较类似,那他们之间除了长的不一样外,还有什么区别吗?
- Java 9中推出List.of创建的是不可变集合,而Arrays.asList是可变集合(长度不可变,值可变)
- List.of和Arrays.asList都不允许add和remove元素,但Arrays.asList可以调用set更改值,而List.of不可以,会报java.lang.UnsupportedOperationException异常
- List.of中不允许有null值,Arrays.asList中可以有null值
4.接口的改变
Java 8 支持接口的默认方法和静态方法 -----> Java 9 可定义 private 私有方法。
接口中私有方法的特点:
- 私有方法不能定义为抽象的
- 私有方法只能在接口内部使用,实现该接口的类或其他外部类无法调用这些方法
- 私有方法不会继承给接口的子接口,每个接口都必须自己定义自己的私有方法。
public interface UserService {// 普通抽象方法,需要被子类实现User getUser();// 私有方法private void helloPri(){System.out.println("===>执行私有方法");}//默认方法default void init(){System.out.println("===>执行默认方法");// 调用私有方法helloPri();}// 私有静态方法private static void stcPri(){System.out.println("===>执行私有静态方法");}// 静态方法static void stcInit(){System.out.println("===>执行静态方法");//调用静态私有方法stcPri();}
}
public class UserServiceImpl implements UserService {@Overridepublic User getUser() {User user = new User();user.setName("张三");user.setAge("18");System.out.println(user);return user;}
}
public static void main(String[] args) {UserService userService = new UserServiceImpl();// 调用抽象方法的实现userService.getUser();// 调用默认方法userService.init();// 调用静态方法UserService.stcInit();
}
5.Stream API
Java 9 对 Stream API 做了一些增强。
ofNullable
static <T> Stream<T> ofNullable(T t) 返回包含单个元素的顺序Stream ,如果非空,否则返回空Stream。
//当需要对可能为null的元素进行流操作时,使用ofNullable可以省去显式的空值判断,使代码更加简洁:
List<String> list = null;
long count = Stream.of(list).flatMap(Collection::stream).count();
System.out.println(count); // 抛出空指针异常List<String> list = null;
long count = Stream.ofNullable(list).flatMap(Collection::stream).count();
System.out.println(count); // 输出0,不会抛出异常
iterate
Stream类的iterate方法新增了一个重载版本,这个版本比原来的iterate方法更加灵活,因为它允许指定一个终止条件。
public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
- seed 初始种子值。
- hasNext 用来判断何时结束流,如果 hasNext 返回 true,则next 函数就会继续生成下一个元素;一旦 hasNext 返回 false,序列生成将停止。
- next函数用来计算下一个元素值。
// jdk8
Stream.iterate(1,i -> i + 1).limit(10).forEach(System.out::println);//jdk9 重载方法
Stream.iterate(1, i -> i <= 10, i -> i + 1).forEach(System.out::println);//输出结果
1
2
3
4
5
6
7
8
9
10
takeWhile
//takeWhile允许你根据给定的条件[截取]流中的元素,直到遇到第一个不满足条件的元素为止,并返回一个新的流,其中包含了前缀元素
Stream<T> takeWhile(Predicate<? super T> predicate)
Stream.of(1,2,3,4,5,6,7,8,9).takeWhile(n -> n < 5).forEach(System.out::println);
// 输出:
1
2
3
4
dropWhile
//dropWhile允许你根据给定的条件[跳过]流中的元素,直到遇到第一个不满足条件的元素为止,并返回一个新的流,其中包含了剩余的元素。
Stream<T> dropWhile(Predicate<? super T> predicate)
Stream.of(1,2,3,4,5,6,7,8,9,1).dropWhile(n -> n < 5).forEach(System.out::println);
// 输出:
5
6
7
8
9
1
6.Optional新增方法
Optional增加了三个有用的API。
stream()
:Optional现在可以转Stream。ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
:如果有值了怎么消费,没有值了怎么消费。or(Supplier<? extends Optional<? extends T>> supplier)
: 该方法接受一个 Supplier作为参数,如果当前Optional有值就返回当前Optional,当前Optional 为空时,将执行Supplier获取一个新的Optional 对象。可以用于提供默认值。
long count = Optional.of("hello").stream().count();
Optional.ofNullable(null).ifPresentOrElse(value -> {System.out.println("有值:" + value);
}, ()->{System.out.println("没有值...");
}); // 没有值...
String str = null;
Optional<String> optional = Optional.ofNullable(str).or(() -> {return Optional.ofNullable("默认值");
});
System.out.println(optional.get()); //默认值
7.Deprecated注解
@Deprecated 注解用于标记过时的 API,Java 9 对其进行了改进,增加了两个的属性:
- since :指明从哪个版本开始 API 被弃用。
- forRemoval:指出这个 API 是否计划在未来的版本中被移除。
比如: Thread类的destory()、stop()。
8.钻石操作符<>
在 Java 7 中引入的钻石操作符简化了泛型实例的创建,但它不能用于匿名内部类。由于这个限制,开发者不得不在使用匿名内部类时指定泛型参数,这增加了代码的冗余和复杂性。
static class InnerCls<T>{}public static void main(String[] args) {InnerCls<String> innerCls = new InnerCls<String>() {};
}
在 Java 9 中,钻石操作符得到了改进,允许与匿名内部类配合使用。现在,当我们实例化一个具有泛型参数的匿名内部类时,无需显式指定这些参数,因为 Java 编译器能够利用上下文信息自动推导出正确的类型。
static class InnerCls<T>{}public static void main(String[] args) {InnerCls<String> innerCls = new InnerCls<>() {};
}
9.异常处理
try-with-resources是JDK7中一个新的异常处理机制,它能够很容易得关闭在try-catch语句中使用得资源。try-with-resources语句确保了每个资源在语句结束时关闭。所有实现了java.lang.AutoCloseable
接口得对象,可以使用作为资源。
try-with-resources声明在JDK9已得到改进。如果你已经有一个资源时final或等效于final变量,你可以在try-with-resources语句中使用该变量,而无需在try-with-resources语句中声明一个新变量。
public static void main(String[] args) throws FileNotFoundException {//jdk8以前try (FileInputStream fileInputStream = new FileInputStream("");FileOutputStream fileOutputStream = new FileOutputStream("")) {} catch (IOException e) {e.printStackTrace();}//jdk9FileInputStream fis = new FileInputStream("");FileOutputStream fos = new FileOutputStream("");//多资源用分号隔开try (fis; fos) {} catch (IOException e) {e.printStackTrace();}}
10.String底层变更
在 JDK 9 中,String
类的底层实现发生了重大变化,从 char[]
改为 byte[]
,并新增了一个 coder
字段来表示编码方式。
编码方式:
- Latin-1 (ISO-8859-1):如果字符串中的字符都在 Latin-1 范围内(即 ASCII 字符),每个字符占用 1 个字节。
- UTF-16:如果字符串中包含超出 Latin-1 范围的字符(如中文等),则使用 UTF-16 编码,每个字符占用 2 个字节或 4 个字节。
code属性:
0
表示 Latin-1 编码。1
表示 UTF-16 编码。
大多数字符串只包含 Latin-1 范围内的字符,使用 byte[]
存储可以显著减少内存占用。例如,一个包含大量 ASCII 字符的字符串,内存占用可以减少一半。这不仅节省了内存,还减少了垃圾回收(GC)的次数,从而提高了性能。
length()计算长度:
- 如果
coder
为0
(Latin-1 编码),则length()
返回byte[]
数组的长度。 - 如果
coder
为1
(UTF-16 编码),则length()
返回byte[]
数组长度的一半(因为每个字符占用 2 个字节)。
public final class String implements Serializable, Comparable<String>, CharSequence {@Stableprivate final byte[] value;private final byte coder;private int hash;static final byte LATIN1 = 0;static final byte UTF16 = 1;// ....
}
11.不能使用下划线作为变量名
在早期版本的 Java 中,下划线(_)已用作标识符或创建 变量名称。从 Java 9 开始,下划线字符是一个保留关键字,不能用作标识符或变量名。如果我们使用单个下划线作为标识符,程序将无法编译并抛出编译时错误,因为现在它是一个 关键字,并且在 Java 9 或更高版本中不能用作变量名称。
public static void main(String[] args) {int _ = 2;
}
12.try-with-resources 优化
在Java 7 中引入了try-with-resources功能,保证了每个声明了的资源在语句结束的时候都会被关闭。
任何实现了java.lang.AutoCloseable接口的对象,和实现了java.io.Closeable接口的对象,都可以当做资源使用。
在Java 7中需要这样写:
try (BufferedInputStream bi = new BufferedInputStream(System.in);BufferedInputStream bi2 = new BufferedInputStream(System.in)) {// do something
} catch (IOException e) {e.printStackTrace();
}
而到了Java 9无需为 try-with-resource 临时声明变量,简化为:
BufferedInputStream bi = new BufferedInputStream(System.in);
BufferedInputStream bi2 = new BufferedInputStream(System.in);
try (bi;bi2) {// do something
} catch (IOException e) {e.printStackTrace();
}
13.CompletableFuture增强
CompletableFuture 是 Java 8 中引入用于处理异步编程的核心类,它引入了一种基于 Future 的编程模型,允许我们以更加直观的方式执行异步操作,并处理它们的结果或异常。
但是在实际使用过程中,发现 CompletableFuture 还有一些改进空间,所以 Java 9 对它做了一些增强,主要内容包括:
- 新的工厂方法
- 支持延迟执行和超时(timeout)机制
- 支持子类化
新的工厂方法:
completedFuture(U value)
:创建一个已经完成的CompletableFuture
实例failedFuture(Throwable ex)
:创建一个异常完成的CompletableFuture
实例。completedStage(U value)
:返回完成的CompletionStage
。failedStage(Throwable ex)
:返回异常完成的CompletionStage
。
支持超时机制:
如果执行超时, orTimeout() 方法直接抛出了一个异常,而 completeOnTimeout() 方法则是返回默认值
// 允许为 CompletableFuture 设置一个超时时间。如果在指定的超时时间内未完成,将抛出 TimeoutException 异常
public CompletableFuture<T> orTimeout(long timeout, TimeUnit unit)
// 允许为 CompletableFuture 设置一个超时时间。如果在指定的超时时间内未完成,则返回默认值value
public CompletableFuture<T> completeOnTimeout(T value, long timeout, TimeUnit unit)
支持延迟执行:
CompletableFuture 类通过 delayedExecutor() 方法提供了对延迟执行的支持,该方法负责生成一个具有延后执行功能的 Executor,使得任务可以在未来指定的时间点才开始执行。
public static Executor delayedExecutor(long delay, TimeUnit unit)
支持子类化:
Java 9 为 CompletableFuture
做了一些改进,使得 CompletableFuture
可以被更简单地继承。
-
newIncompleteFuture()
:创建一个新的、不完整的CompletableFuture
实例。子类可以重写这个方法来返回CompletableFuture
的子类实例,允许在整个CompletableFuture
API 中自定义实例的行为。public <U> CompletableFuture<U> newIncompleteFuture()
-
defaultExecutor()
:提供CompletableFuture
操作的默认执行器。子类可以重写此方法以提供不同的默认执行器,这对于定制任务执行策略特别有用。public Executor defaultExecutor()
-
copy()
:创建一个与当前CompletableFuture
状态相同的新实例。子类可以重写此方法以确保复制的实例是特定子类的实例,而不仅仅是CompletableFuture
。public CompletableFuture copy()
public class MyCompletableFuture<U> extends CompletableFuture<U> {@Overridepublic Executor defaultExecutor() {return Executors.newSingleThreadExecutor();}@Overridepublic CompletableFuture<U> newIncompleteFuture() {return new MyCompletableFuture<>();}@Overridepublic CompletableFuture<U> copy() {return new MyCompletableFuture<>();}
}public static void main(String[] args) throws Exception {MyCompletableFuture<String> future = new MyCompletableFuture<>();future.completeAsync(() -> {System.out.println(Thread.currentThread());return "hello world";}).thenAccept(System.out::println);future.join();
}
14.垃圾收集器
- G1 垃圾收集器成为默认垃圾收集器
- 背景:从 JDK 9 开始,G1(Garbage-First)垃圾收集器成为 32 位和 64 位服务器配置的默认垃圾收集器。
- 动机:限制 GC 暂停时间通常比最大化吞吐量更重要。G1 作为一个低暂停时间的收集器,相比面向吞吐量的并行 GC,能够为大多数用户提供更好的整体体验。
- 改进:
- 并行全GC:G1 在 JDK 9 中引入了并行化的全GC,多个线程可以同时工作,加快垃圾回收速度,减少 GC 暂停时间。
- 全GC的预测:G1 能够预测何时可能发生全GC,并提前进行准备工作,减少全GC的停顿时间。
- 改进的内存回收效率:优化了内存回收效率,尤其是在处理大对象和字符串时。
- 更好的自适应调整:改进了并发标记周期,更准确地调整 Young GC 和 Mixed GC 的频率和持续时间。
- 降低的内存占用:减少了一些内部数据结构的内存占用,降低了整体内存消耗。
- 改进的 G1 日志记录:提供了更多关于垃圾回收过程的信息,便于性能调优和问题诊断。
- CMS 垃圾收集器被标记为过时
- 背景:CMS(Concurrent Mark-Sweep)垃圾收集器在 JDK 9 中被标记为过时,计划在未来的主要版本中停止支持。
- 动机:减少 GC 代码库的维护负担,并加速新垃圾收集器的开发。G1 垃圾收集器旨在长期替代大多数 CMS 的使用场景。
- 移除JDK 8中已弃用的垃圾收集器(GC)组合。
这意味着以下GC组合不再存在:
- DefNew + CMS DefNew CMS
- DefNew:这是
Serial
收集器的新生代版本,采用复制算法,单线程执行。它会在垃圾回收时暂停所有用户线程(STW),适合单核处理器或对响应时间要求不高的场景。 - CMS:全称为 Concurrent Mark Sweep,是老年代的垃圾收集器,采用标记-清除算法。它可以在垃圾回收过程中与用户线程并发执行,从而减少停顿时间,适合对延迟敏感的应用。
- DefNew:这是
- ParNew + SerialOld ParNew SerialOld
- ParNew:这是
Serial
收集器的多线程版本,用于新生代的垃圾回收。它使用复制算法,并且可以利用多核处理器来提高回收效率。 - SerialOld:这是
Serial
收集器的老年代版本,采用标记-整理算法,单线程执行。
- ParNew:这是
- Incremental CMS 增量CMS
- Incremental CMS 是 Concurrent Mark Sweep (CMS) 垃圾回收器的一个变种,旨在减少垃圾回收过程中对应用程序性能的影响。它通过让垃圾回收线程和用户线程交替运行,尽量减少垃圾回收线程独占资源的时间。