JDK8新特性
JDK9新特性
JDK10新特性
JDK11新特性
JDK12新特性
JDK13新特性
JDK14新特性
JDK15新特性
JDK16新特性
JDK17新特性
JDK18新特性
JDK19新特性
JDK20新特性
JDK21新特性
JDK9提供了超过150项新功能特性,包括倍受期待的模块化系统,可交互的REPL工具:jshell,JDK编译工具,Java公共API和私有代码,以及安全增强、扩展提升、性能管理改善等。可以说Java9是一个庞大的系统工程,完全做了一个整体的改变。
具体来讲:
● JDK和JRE的改变
● 模块化系统
● JShell命令
● 多版本兼容jar包
● 接口的私有方法
● 砖石操作符的使用升级
● try语句
● 下划线使用限制
● String储存结构变更
● 便利的集合特性:of()
● 增强的Stream API
● 多分辨率图像API
● 全新的HTTP客户端API
● Deprecated的相关API
● 智能Java编译工具
● 统一的JVM日志系统
● javadoc的HTML5支持
● javascript引擎升级:Nashorn
● Java的动态编译器
说明:
这个新特性只要了解下就可以了,这个目录结构是方便为了接下来新特性做保证。jdk8和jdk9的安装还是会额外安装一次jre。
众所周知,Java 已经发展超过 20 年(95 年最初发布),Java 和相关生态在不断丰富的同时也越来越暴露出一些问题:
● Java 运行环境的膨胀和臃肿。每次 JVM 启动的时候,至少会有 30~60MB 的内存加载,主要原因是 JVM 需要加载 rt.jar,不管其中的类是否被classloader 加载,第一步 整个 jar 都会被 JVM 加载到内存当中去(而模块化可以根据模块的需要加载程序运行需要的 Class)
● 当代码库越来越大,创建复杂,盘根错节的「面条式代码」的几率呈指数级的增长。不同版本的 类库交叉依赖 导致让人头疼的问题,这些都 阻碍了 Java 开发和运行效率的提升
● 很难真正地对代码进行封装,而系统并没有对不同部分(也就是 JAR 文件)之间的依赖关系有个明确的概念。每一个公共类都可以被类路径之下任何其它的公共类所访问到,这样就会导致无意中使用了并不想被公开访问的 API
● 类路径本身也存在问题: 你怎么知晓所有需要的 JAR 都已经有了,或者是不是会有重复的项呢
● 同时,由于 兼容性等各方面的掣肘,对 Java 进行大刀阔斧的革新越来越困难,Jigsaw 从 Java 7 阶段就开始筹备,Java 8 阶段进行了大量工作,终于在 Java 9 里落地,一种千呼万唤始出来的意味。
● Jigsaw 项目(后期更名为 Modularity)的工作量和难度大大超出了初始规划。JSR 376 Java 平台模块化系统(JPMS,Java Platform Module System)作为 Jigsaw 项目的核心,其主体部分被分解成 6 个 JEP(JDK Enhancement Proposals)。
● 作为 Java 9 平台 最大的一个特性,随着 Java 平台模块化系统的落地,开发人员无需再为不断膨胀的 Java 平台苦恼,例如,您可以 使用 jlink 工具,根据需要定制运行时环境。这对于拥有大量镜像的容器应用场景或复杂依赖关系的大型应用等,都具有非常重要的意义。
● 本质上讲,模块(module)的概念,其实就是 package 外再裹一层,也就是说,用模块来管理各个 package,通过声明某个 package 暴露,不声明默认就是隐藏。因此,模块化使得代码组织上 更安全,因为它可以指定哪些部分可以暴露,哪些部分隐藏。
模块独立、化繁为简。
模块化(以 Java 平台模块系统的形式)将 JDK 分成一组模块,可以在编译时,运行时或者构建时进行组合。
● 主要目的在于减少内存的开销
● 只须必要模块,而非全部 JDK 模块,可 简化各种类库和大型应用的开发和维护
● 改进 Java SE 平台,使其可以 适应不同大小的计算设备
● 改进其安全性,可维护性,提高性能
idea在jdk9安装环境下新建的springboot3Demo项目(jdk17环境也可以)
springboot3Demo中新建两个Module
● java9provider:该模块主要提供Person类
● java9consumer:该模块主要使用Person类
Person类代码:
package test.java9provider;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
public class Person implements Serializable {
private Integer age;
private String name;
public Person(Integer age, String name) {
this.age = age;
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
java9consumer中使用Person类会发现如下截图问题,Person类在另一个模块中,无法直接使用。
jdk9有了新的方案,就是模块化系统。
分别如图在两个模块下的src文件夹右键new一个module-info.java文件出来。
java9provider模块中module-info.java文件代码:
module java9provider {
// 导出相应的包(Person所在的包)
exports test.java9provider;
}
java9consumer模块中module-info.java文件代码:
module java9consumer {
// 需要的模块名称
requires java9provider;
}
java9consumer模块中使用Person代码:
package test.java9consumer;
import test.java9provider.Person;
public class ModuleTest {
public static void main(String[] args) {
Person person = new Person(18,"张三");// 仍然需要点一下Add dependency on module
System.out.println(person);
}
}
测试结果如下图,可以正常使用Person类了,并且打印出Person信息:
像 Python 和 Scala 之类的语言早就有交互式编程环境 REPL(read - evaluate - print - loop)了,以交互式的方式对语句和表达式进行求值。开发者只需要输入一些代码,就可以在编译前获得对程序的反馈。而 之前的 Java 版本要想执行代码,必须创建文件、声明类、提供测试方法方可实现。
即写即得、快速运行
Java 9 中终于拥有了 REPL 工具:JShell。利用 JShell 在没有创建类的情况下直接声明变量,计算表达式,执行语句。即开发时可以在命令行里直接运行 Java 的代码,而无需创建 Java 文件,无需跟人解释 public static void main(String[] args) 这句话。
● JShell 也可以从 文件中加载语句 或者将语句保存到文件中。
● JShell 也可以是 Tab 键进行自动补全 和 自动添加分号。
进入jdk9安装目录bin下面找到jshell.exe双击运行(可以用jdk17代替测试)
和在 Java 文件写代码一样,JShell 会自动执行输入的代码,检查代码语法,并且代码保存起来,当退出JShell后,再清空代码。
注意:在 JShell 环境下,语句末尾的 ; 是可选的。但推荐还是最好加上。提高代码可读性。
/imports 可以查看默认已经导入的包,import 包名 导入具体的包。
/tab 可以查看其他功能
tab的功能和使用Linux系统的功能一样,可以补全单词等等。
/list 可以看到当前有效的代码
/vars 可以看到定义的变量
/methods 可以看到定义的方法
/edit add 可以单独弹出add方法的编辑框,编辑处理
C盘事先编辑好的hello.java文件,里面就一句代码 System.out.println(“JShell very good”);
/open C:\hello.java 可以直接加载外部hello.java源代码
正常写代码的时候,有些代码会有异常提示,需要用try等处理异常
在jshell中则不用专门写try,因为在后台将异常代码隐藏了,实际上是有try等代码的效果的,/list也是看不到的。
当一个新版本的 Java 出现的时候,你的库用户要花费数年时间才会切换到这个新的版本。这就意味着 库得去向后兼容你想要支持的最老的 Java 版本(许多情况下就是 Java 6 或者 Java7) 。这实际上意味着未来的很长一段时间,你都不能在库中运用 Java 9 所提供的新特性。幸运的是,多版本兼容 jar 功能能让你创建仅在特定版本的 Java 环境中运行库程序选择使用的 Class 版本。
比如一个第三方项目hello.jar,这个项目使用了java8类库中的一个类,假设这个类有打印输出,打印输出java8,在java9时,这个类更新了,打印输出java9。
当我们自己开发环境是jdk8的时候,引入这个第三方hello.jar包,打印输出就是java8。
当我们自己开发环境是jdk9的时候,引入这个第三方hello.jar包,打印输出就是java9。
jdk9以后支持了多版本兼容jar包,其实就是打包出来的hello.jar包多了一些类文件,比如jdk类库中的类A,默认打包后,只有一个类A.class,但在多版本兼容方式jdk9版本打包后,就会有两个类A.class,一个类A是默认的,对应jdk8版本的,另一个类A是jdk9版本的。以此类推,如果是多版本兼容方式jdk10版本打包后,就会有三个类A,分别是jdk8版本的类A.calss、jdk9版本的类A.calss、jdk10版本的类A.calss,在不同开发环境下运行,功能可能不一样。
点击查看 9分钟案例视频 更容易理解
Java 8 中规定接口中的方法除了抽象方法之外,还可以定义静态方法和默认的方法。一定程度上,扩展了接口的功能,此时的接口更像是一个抽象类。
在 Java 9 中,接口更加的灵活和强大,连方法的访问权限修饰符都可以声明为 private 的了,此时方法将不会成为你对外暴露的 API 的一部分。
接口语法:
jdk7:只能声明全局常量(public static final)和抽象方法(public abstract)
void method1();
jdk8:增加了静态方法和默认方法(可以有实体方法了,不全是抽象方法)
// 静态方法
public static void method2(){
System.out.println("method2");
}
// 默认方法
default void method3(){
System.out.println("method3");
}
jdk9:增加了私有方法(权限修饰符可以为private了,jdk9之前都是public,只是public可以省略不写)
private void method4();
List<String>中的<>这个符号就是砖石操作符,其实就是表示泛型的操作符。
package com.zt.jdk9NewFeatures;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Jdk9NewFeatures {
private List<String> flattenStrings(List<String>... lists) {
Set<String> set1 = new HashSet<>();// 编译不报错,类型推断
Set<String> set2 = new HashSet<String>(){};// 编译不报错,这边其实是想写匿名内部类
Set<String> set3 = new HashSet<>(){};// 编译报错,这边其实是想写匿名内部类,如果在jdk8不单独声明泛型中的类型,就会报错,而在jdk9中新增了这项功能
for (List<String> list : lists) {
set1.addAll(list);
}
return new ArrayList<>(set1);
}
}
在jdk8上面代码在不声明泛型类型的时候,写匿名内部类会报错,而在jdk9中新增了特性,我们将能够与匿名实现类共同使用砖石操作符。
package com.zt.jdk9NewFeatures;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Jdk9NewFeatures {
private List<String> flattenStrings(List<String>... lists) {
Set<String> set = new HashSet<>(){
// 创建一个继承于HashSet的匿名子类对象
// 可以覆写一些HashSet的方法
};
for (List<String> list : lists) {
set.addAll(list);
}
return new ArrayList<>(set);
}
}
在jdk8中try语句可以自动关闭资源,在jdk9中对该功能进一步进行了加强。
public static void main(String[] args) {
// jdk8版本写法
InputStreamReader reader1 = null;
try {
reader1 = new InputStreamReader(System.in);
reader1.read();// 读取数据
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 资源关闭操作
if (reader1 != null) {
try {
reader1.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// jdk8升级写法,要求资源对象的实例化必须放在try的一对()内完成,会主动关闭资源。
try (InputStreamReader reader2 = new InputStreamReader(System.in); OutputStreamWriter writer2 = new OutputStreamWriter(System.out)) {
reader2.read();// 读取数据
} catch (IOException e) {
throw new RuntimeException(e);
}
// jdk9中,可以在try()中调用已经实例化的资源对象,会主动关闭资源。
InputStreamReader reader3 = new InputStreamReader(System.in);// 此时的reader3是个final的,因为try里面关闭的是它,不能再赋值修改了。
OutputStreamWriter writer3 = new OutputStreamWriter(System.out);
try (reader3; writer3) {
reader3.read();// 读取数据
} catch (IOException e) {
throw new RuntimeException(e);
}
}
在jdk8中,标识符可以独立使用“_”来命名:
// 在jdk8中,标识符可以独立使用“_”来命名
String _ = "hello";
System.out.println(_);
在jdk9中,“_”不再可以单独命名标识符了,如果使用会报错:
// 在jdk9中,“_”不再可以单独命名标识符了,如果使用会报错:
String _ = "hello";
System.out.println(_);
String 类的jdk8实现将字符存储在字符数组cahr[]中,每个字符使用两个字节(16 位)。目前从许多不同的应用程序中收集的数据表明字符串是堆使用的主要组成部分,而且最重要的是对象仅包含拉丁字母 。这样的字符只需要一个字节的存储空间,因此只占内部字符数组空间的一半,许多这样的字符串对象将被闲置,空间被浪费。
jdk9建议更改 String 类的内部表示形式从 UTF-16 字符数组到字节数组再加上编码标志领域新的字符串类将存储编码为 ISO-8859-1 / 拉丁语-1(每个字符一个字节),或作为 UTF-16(两个字节每个字符),基于字符串的内容。编码标志将指示使用哪种编码。
结论:String 再也不用 char[] 来存储,改成了 byte[] 加上编码标记,节约了一些空间。
jdk8的String部分代码截图:
jdk9的String部分代码截图:(实际用jdk17截图的)
同样的,StringBuffer和StringBuilder也变化为了byte[]
String:不可变的字符序列,新的字符串会重新开辟内存空间;
StringBuffer:可变的字符序列,线程安全的,效率低;
StringBuilder:可变的字符序列,线程不安全的,效率高;
以上3个类在从cahr[]变为byte[]以后,开发者使用感受不到任何变化。
jdk8中要创建一个只读不可改变的集合,必须构造和分配它,然后添加元素,最后包装成一个不可修改的集合。
比如:
public static void main(String[] args) {
// 创建一个只读特点的集合
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
// 调用Collections中的方法,将list变为只读的
List<String> listNew = Collections.unmodifiableList(list);
listNew.add("王五");// 只读的再去增加就会报异常 java.lang.UnsupportedOperationException
listNew.forEach(System.out::println);
}
// 创建只读list
List<Integer> list = Collections.unmodifiableList(Arrays.asList(1, 2, 3));
// 创建只读set
Set<Integer> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(1, 2, 3)));
// 创建只读Map
Map<String, String> map = Collections.unmodifiableMap(new HashMap<String, String>() {
{
put("k1", "v1");
put("k2", "v2");
}
});
jdk9中引入了新方法,使得类似事情更容易表达。
jdk9中给各个集合引入了 of() 静态方法,该方法代替 Collections.unmodifiableXXX 创建出 只读、不可改变 的集合。
// jdk9中,创建一个只读特点的集合
// 创建只读list
List<Integer> list = List.of(1, 2, 3);
// 创建只读set
Set<Integer> set = Set.of(1, 2, 3);
// 创建只读Map(方式一)
Map<String, String> map1 = Map.of("k1", "v1", "k2", "v2");
// 创建只读Map(方式二)
Map<String, String> map2 = Map.ofEntries(Map.entry("k1", "v1"), Map.entry("k2", "v2"));
Java 的 Steam API 是 Java 标准库最好的改进之一,让开发者能够快速运算,从而能够有效的利用数据并行计算。jdk8 提供的 Steam 能够利用多核架构实现声明式的数据处理。
在 jdk9中,Stream API 变得更好,Stream 接口中添加了 4 个新的方法:dropWhile、takeWhile、ofNullable,还有个 iterate 方法的新重载方法,可以让你提供一个 Predicate(判断条件)来指定什么时候结束迭代。
除了对 Stream 本身的扩展,Optional 和 Stream 之间的结合也得到了改进。现在可以通过 Optional 的新方法 stream() 将一个 Optional 对象转换为一个(可能是空的)Stream 对象。
// jdk9新增 Stream API
// takeWhile() 用于从 Stream 中获取一部分数据,接收一个 Predicate 来进行选择。在有序的 Stream 中,takeWhile 返回从开头开始的尽量多的元素。
List<Integer> list = Arrays.asList(45, 49, 80, 110, 23, 54, 33);
list.stream().takeWhile(x -> x < 50).forEach(System.out::println);// 45 49
// 备注:到80的时候就不满足了,不管后面的元素是否满足,都不会取了,和filter过滤是不一样的。
list.stream().filter(x -> x < 50).forEach(System.out::println);// 45 49 23 33
// dropWhile() 行为与 takeWhile 相反,返回剩余的元素。
list.stream().dropWhile(x -> x < 50).forEach(System.out::println);// 80 110 23 54 33
// ofNullable() jdk8 中 Stream 不能完全为 null,否则会报空指针异常。而 jdk9 中的 ofNullable 方法允许我们创建一个单元素 Stream,可以包含一个非空元素,也可以创建一个空 Stream。
Stream<Integer> stream1 = Stream.of(1, 2, 3, null);// jdk8中多个元素中可以有一个是null编译不会报错
System.out.println(stream1.count());// 4
//Stream stream2 = Stream.of(null);// jdk8中单元素为null编译报错,报空指针异常
//System.out.println(stream2.count());// 上面一句就报错了
Stream<Integer> stream3 = Stream.ofNullable(1);// jdk9中新增的ofNullable方法,只能创建一个单元素的Stream
System.out.println(stream3.count());// 1
Stream<Integer> stream4 = Stream.ofNullable(null);// jdk9中新增的ofNullable方法可以存放为null单元素
System.out.println(stream4.count());// 0
// iterator()重载方法
// jdk8的时候iterator()方法
//Stream.iterate(0, x -> x + 1).forEach(System.out::println);// 从0开始,一直往下执行,所以一般要和limit配合限制一下
Stream.iterate(0, x -> x + 1).limit(2).forEach(System.out::println);// 0 1
// jdk9新增了iterator()重载方法,增加了一个判断条件参数,就不用用limit来限制了
Stream.iterate(0, x -> x < 2, x -> x + 1).forEach(System.out::println);// 0 1
// Optional 类中 新增了stream() 的使用
List<Integer> list2 = Arrays.asList(1, 2, 3);
Optional<List<Integer>> optional = Optional.ofNullable(list2);
Stream<List<Integer>> stream21 = optional.stream();// 注意流的元素是List , 不是Integer,所以元素数量只有一个 [1, 2, 3]
stream21.forEach(System.out::println);// [1, 2, 3]
Stream<Integer> stream22 = optional.stream().flatMap(x -> x.stream());
stream22.forEach(System.out::println);// 1 2 3
在 Mac 上,JDK 已经支持视网膜显示,但在 Linux 和 Windows 上,它并没有。在那里,Java 程序在当前的高分辨率屏幕上可能看起来很小,不能使用它们。这是因为像素用于这些系统的大小计算(无论像素实际有多大)。毕竟,高分辨率显示器的有效部分是像素非常小。
JEP 263 以这样的方式扩展了 JDK,即 Windows 和 Linux 也考虑到像素的大小。为此,使用比现在更多的现代 API:Direct2D for Windows 和 GTK +,而不是 Xlib for Linux 图形,窗口和文本由此自动缩放。
JEP 251 还提供处理多分辨率图像的能力,即 包含不同分辨率的相同图像的文件。根据相应屏幕的 DPI 度量,然后以适当的分辨率使用图像。
● 新的 API 定义在 java.awt.image 包下
● 将不同分辨率的图像封装到一张(多分辨率的)图像中,作为它的变体
● 获取这个图像的所有变体
● 获取特定分辨率的图像变体,表示一张已知分辨率单位为 DPI 的特定尺寸大小的逻辑图像,并且这张图像是最佳的变体
● 基于当前屏幕分辨率大小和运用的图像转换算法,java.awt.Graphics 类可以从接口 MultiResolutionImage 获取所需的变体
● MultiResolutionImage 的基础实现是 java.awt.image.BaseMultiResolutionImage。
HTTP,用于传输网页的协议,早在 1997 年就被采用在目前的 1.1 版本中。直到 2015 年,HTTP2 才成为标准。
HTTP/1.1 和 HTTP/2 的主要区别是如何在客户端和服务器之间构建和传输数据。HTTP/1.1 依赖于请求/响应周期。HTTP/2 允许服务器 push 数据:它可以发送比客户端请求更多的数据。这使得它可以优先处理并发送对于首先加载网页至关重要的数据。
jdk9 中有新的方式来处理 HTTP 调用。它提供了一个新的 HTTP 客户端(HttpClient),它将 替代仅适用于 blocking 模式的 HttpURLConnection(HttpURLConnection 是在 HTTP 1.0 的时代创建的,并使用了协议无关的方法),并提供 对 WebSocket 和 HTTP/2 的支持。
此外,HTTP 客户端还提供 API 来处理 HTTP/2 的特性,比如流和服务器推送等功能。
全新的 HTTP 客户端 API 可以从 jdk.incubator.httpclient 模块中获取。因为在默认情况下,这个模块是不能根据 classpath 获取的,需要使用 add modules 命令选项配置这个模块,将这个模块添加到 classpath 中。
package com.zt.jdk9NewFeatures;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.*;
import java.util.stream.Stream;
public class Jdk9NewFeatures {
public static void main(String[] args) {
// jdk9 中 使用 HttpClient 替换原有的 HttpURLConnection
try {
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder(URI.create("https://www.baidu.com")).GET().build();
HttpResponse<String> response = null;
response = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());// 200
System.out.println(response.version().name());// HTTP_1_1
System.out.println(response.body());// 内容太长,看打印输出
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
打印输出:
200
HTTP_1_1
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129>