龙空技术网

讲解JDK从8以后(包含8)各个版本的各种新特性

架构师聊技术 434

前言:

此时大家对“jsa标签”大概比较看重,各位老铁们都需要了解一些“jsa标签”的相关资讯。那么小编也在网摘上网罗了一些有关“jsa标签””的相关文章,希望姐妹们能喜欢,大家一起来学习一下吧!

1. JDK

JDK 全称 Java Development Kit,是 Java 开发环境。我们通常所说的 JDK 指的是 Java SE (Standard Edition) Development Kit。除此之外还有 Java EE(Enterprise Edition)和 Java ME(Micro Edition platforms)。

1.1 Java 的发布周期

版本

发布时间

名称

JDK Beta

1995

WebRunner

JDK 1.0

1996.1

Oak

JDK 1.1

1997.2

J2SE 1.2

1998.12

playground

J2SE 1.3

2000.5

Kestrel

J2SE 1.4

2002.2

Merlin

J2SE 5.0

2004.9

Tiger

Java SE 6

2006.12

Mustang

Java SE 7

2011.7

Dolphin

Java SE 8(Lts)

2014.3

Java SE 9

2017.9

Java SE 10

2018.3

Java SE 11(Lts)

2018.9

Java SE 12

2019.3

Java SE 13

2019.9

Java SE 14

2020.3

Java SE 15

2020.9

1.2 Java发展过程中的重要节点

1995年: alpha 和 beta Java 公开版本发布,取名为 WebRunner。

1996年: Java 第一个版本发布,取名叫 Oak。但是第一个稳定版本是 JDK 1.0.2,被称做 Java 1。

1998.12.8: 发布了 J2SE 1.2。这个版本到 J2SE 5.0 更名为 Java 2。其中的 SE 指的是 Standard Edition,为了区别于 J2EE(Enterprise Edition)和 J2ME(Micro Edition)。

2000.5: 发布了 J2SE 1.3,其中包含了 HotSpot JVM。而 HotSpot JVM 首次发布是在 1999.4,名为 J2SE 1.2 JVM。

2004.9.30: 发布了 J2SE 5.0。为什么这个版本命名和前面几个版本不一样呢?这个版本原本计划以 1.5 命名的,沿用以前的命名方式。但是为了更好的反映这个版本的成熟度,所以改名为 5.0。

这个版本以后,有了一个新的版本控制系统,5.0 用来表示产品版本,用来表示稳定的 J2SE 版本,而 1.5.0 用来表示开发者版本,也就是 Java 5.0 = JDK 1.5.0。

2006.12.11: J2SE 改名为 Java SE,版本号去掉了 .0。此后对应版本就是 Java 6 = JDK 1.6,Java 7 = JDK 1.7。

2011.7.7: 发布 Java SE 7,是一个重大版本更新。更新了众多特性。

2018.3: 发布 Java SE 10。在此之前,Java 基本上是两年一个版本,除了 Java SE 7 经过了五年,Java SE 8 经过了三年。在此之后,就是每六个月发布一次新版本。但是不是每个版本都是 LTS(Long-Term-Support)。按照 Oracle 的计划,每三年会有一个 LTS 版本。最近的 LTS 版本就是 Java SE 11 了。

2. Java SE 8后各个版本介绍2.1 Java SE 8

2.1.1. Lambda 和 函数式接口 Lambda 表达式相信不用再过多的介绍,终于在 Java 8 引入了,可以极大的减少代码量,代码看起来更清爽。

函数式接口就是有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。可以隐式转化为 Lambda 表达式。

我们定义一个函数式接口如下:

@FunctionalInterfaceinterface Operation {    int operation(int a, int b);}// 再定义一个 Class 用来操作 Operation 接口。class Test {    private int operate(int a, int b, Operation operation) {        return operation.operation(a, b);    }}Test test = new Test();// 在 Java 8 之前,我们想要实现 Operation 接口并传给 Test.operate() 方法使用,我们要定义一个匿名类,实现 Operation 方法。test.operate(1, 2, new Operation() {    @Override    public int operation(int a, int b) {        return a + b;    }});// 而使用 Lambda 表达式,我们就可以这样写了:test.operate(1, 2, (a, b) -> a + b);复制代码
2.1.2 方法推导

通过方法引用,可以使用方法的名字来指向一个方法。使用一对冒号来引 "::" 用方法。还是以上面的例子来看,我们再添加几个方法:

@FunctionalInterfaceinterface Operation {    int operation(int a, int b);}interface Creater<T> {    T get();}interface TestInt {    int cp(Test test1, Test test2);}class Test {    public static Test create(Creater<Test> creater) {        return creater.get();    }    private int operate(int a, int b, Operation operation) {        return operation.operation(a, b);    }    private static int add(int a, int b) {        return a + b;    }    private int sub(int a, int b) {        return a - b;    }    public int testM(Test test) {        return 0;    }    public void test(TestInt testInt) {        Test t1 = Test.create(Test::new);         Test t2 = Test.create(Test::new);        testInt.cp(t1, t2);    }}复制代码

那么对应的方法引用有四种:

构造方法引用

使用方式:Class::new

Test test = Test.create(Test::new);复制代码
静态方法引用

使用方式:Class::staticMethod

test.operate(1, 2, Test::add);复制代码
对象的实例方法引用

使用方式:instance::method

test.operate(1, 2, test::sub);复制代码
类的实例方法引用

使用方式:Class::method

test.test(Test::testM);复制代码

其实上面三种方法引用都好理解,最后类的实例方法引用,有两个条件:

首先要满足实例方法,而不是静态方法Lambda 表达式的第一个参数会成为调用实例方法的对象

根据这两点我们看上面的例子,test 方法接受一个 TestInt 实例,用 Lambda 表达式表示就是 (Test t1, Test t2) -> res,而我们调用 test 方法时传入的方法引用是 Test::testM,其参数也是一个 Test 实例,最终 test.test(Test::testM) 的调用效果就是 t1.testM(t2)

2.1.3 接口默认方法和静态方法

Java 8 新增了接口的默认实现,通过 default 关键字表示。同时也可以提供静态默认方法。

public interface TestInterface {    String test();    // 接口默认方法    default String defaultTest() {        return "default";    }    static String staticTest() {        return "static";    }}复制代码
2.1.4 重复注解

Java 8 支持了重复注解。在 Java 8 之前想实现重复注解,需要用一些方法来绕过限制。比如下面的代码。

@interface Author {    String name();}@interface Authors {    Author[] value();}@Authors({@Author(name="a"), @Author(name = "b")})class Article {}复制代码

而在 Java 8 中,可以直接用下面的方式。

@Repeatable(Authors.class)@interface Author {    String name();}@interface Authors {    Author[] value();}@Author(name = "a")@Author(name = "b")class Article {}复制代码

在解析注解的时候,Java 8 也提供了新的 API。

AnnotatedElement.getAnnotationsByType(Class<T>)复制代码
2.1.5 类型注解

Java 8 之前注解只能用在声明中,在 Java 8 中,注解可以使用在 任何地方。

@Author(name="a")private Object name = "";private String author = (@Author(name="a")String) name;复制代码
2.1.6 更好的类型推断

Java 8 对于类型推断做了改进。

比如在 Java 7 中下面的写法:

List<String> stringList = new ArrayList<>();stringList.add("A");stringList.addAll(Arrays.<String>asList());复制代码

在 Java 8 中改进后的写法,可以自动做类型推断。

List<String> stringList = new ArrayList<>();stringList.add("A");stringList.addAll(Arrays.asList());复制代码
2.1.7 Optional

Java 8 中新增了 Optional 类用来解决空指针异常。Optional 是一个可以保存 null 的容器对象。通过 isPresent() 方法检测值是否存在,通过 get() 方法返回对象。

除此之外,Optional 还提供了很多其他有用的方法,具体可以查看文档。下面是一些示例代码。

// 创建一个 String 类型的容器Optional<String> str = Optional.of("str");// 值是否存在boolean pre = str.isPresent();// 值如果存在就调用 println 方法,这里传入的是 println 的方法引用str.ifPresent(System.out::println);// 获取值String res = str.get();// 传入空值str = Optional.ofNullable(null);// 如果值存在,返回值,否则返回传入的参数res = str.orElse("aa");str = Optional.of("str");// 如果有值,对其调用映射函数得到返回值,对返回值进行 Optional 包装并返回res = str.map(s -> "aa" + s).get();// 返回一个带有映射函数的 Optional 对象res = str.flatMap(s -> Optional.of(s + "bb")).flatMap(s -> Optional.of(s + "cc")).get();复制代码
2.1.8 Stream

Java 8 中新增的 Stream 类提供了一种新的数据处理方式。这种方式将元素集合看做一种流,在管道中传输,经过一系列处理节点,最终输出结果。

关于 Stream 提供的具体方法,可以参照 API。下面是一些示例代码。

List<String> list = Arrays.asList("maa", "a", "ab", "c");list.stream()        .filter(s -> s.contains("a"))        .map(s -> s + "aa")        .sorted()        .forEach(System.out::println);System.out.println("####");list.parallelStream().forEach(System.out::println);List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);int res = numbers.stream().map(i -> i + 1).mapToInt(i -> i).summaryStatistics().getMax();System.out.println(res);复制代码
2.1.9 日期时间 API

Java 8 中新增了日期时间 API 用来加强对日期时间的处理,其中包括了 LocalDate,LocalTime,LocalDateTime,ZonedDateTime 等等,关于 API 可以参照官方文档以及这篇博客,写的很详细。

下面是示例代码。

LocalDate now = LocalDate.now();System.out.println(now);System.out.println(now.getYear());System.out.println(now.getMonth());System.out.println(now.getDayOfMonth());LocalTime localTime = LocalTime.now();System.out.println(localTime);LocalDateTime localDateTime = now.atTime(localTime);System.out.println(localDateTime);复制代码
2.1.10 Base64 支持

Java 8 标准库中提供了对 Base 64 编码的支持。具体 API 见可参照文档。下面是示例代码。

String base64 = Base64.getEncoder().encodeToString("aaa".getBytes());System.out.println(base64);byte[] bytes = Base64.getDecoder().decode(base64);System.out.println(new String(bytes));复制代码
2.1.11 并行数组 ParallelSort

Java 8 中提供了对数组的并行操作,包括 parallelSort 等等,具体可参照 API。 Arrays.parallelSort(new int[] {1, 2, 3, 4, 5});

2.1.12 其他特性对并发的增强在java.util.concurrent.atomic包中还增加了下面这些类:

DoubleAccumulator DoubleAdder LongAccumulator LongAdder

提供了新的 Nashorn javascript 引擎提供了 jjs,是一个给予 Nashorn 的命令行工具,可以用来执行 JavaScript 源码提供了新的类依赖分析工具 jdepsJVM 的新特性

JVM内存永久区已经被metaspace替换(JEP 122)。JVM参数 -XX:PermSize 和 –XX:MaxPermSize被XX:MetaSpaceSize 和 -XX:MaxMetaspaceSize代替。 可以看到,Java 8 整体上的改进是很大的,最重要的是引入 Lambda 表达式,简化代码。

其他一些改进可参照:

2.2 Java SE 92.2.1 Jigsaw 模块系统

在 Java 9 以前,打包和依赖都是基于 JAR 包进行的。JRE 中包含了 rt.jar,将近 63M,也就是说要运行一个简单的 Hello World,也需要依赖这么大的 jar 包。在 Java 9 中提出的模块化系统,对这点进行了改善。

关于模块化系统具体可以看看这篇文章。

zhuanlan.zhihu.com/p/24800180

2.2.2 JShell REPL

Java 9 提供了交互式解释器。有了 JShell 以后,Java 终于可以像 Python,Node.js 一样在 Shell 中运行一些代码并直接得出结果了。

2.2.3 私有接口方法,接口中使用私有方法

Java 9 中可以在接口中定义私有方法。示例代码如下:

public interface TestInterface {    String test();    // 接口默认方法    default String defaultTest() {        pmethod();        return "default";    }    private String pmethod() {        System.out.println("private method in interface");        return "private";    }}复制代码
2.2.4 集合不可变实例工厂方法

在以前,我们想要创建一个不可变的集合,需要先创建一个可变集合,然后使用 unmodifiableSet 创建不可变集合。代码如下:

Set<String> set = new HashSet<>();set.add("A");set.add("B");set.add("C");set = Collections.unmodifiableSet(set);System.out.println(set);复制代码

Java 9 中提供了新的 API 用来创建不可变集合。

List<String> list = List.of("A", "B", "C");Set<String> set = Set.of("A", "B", "C");Map<String, String> map = Map.of("KA", "VA", "KB", "VB");复制代码
2.2.5 改进 try-with-resources

Java 9 中不需要在 try 中额外定义一个变量。Java 9 之前需要这样使用 try-with-resources:

InputStream inputStream = new StringBufferInputStream("a");try (InputStream in = inputStream) {    in.read();} catch (IOException e) {    e.printStackTrace();}复制代码

在 Java 9 中可以直接使用 inputStream 变量,不需要再额外定义新的变量了。

InputStream inputStream = new StringBufferInputStream("a");try (inputStream) {    inputStream.read();} catch (IOException e) {    e.printStackTrace();}复制代码
2.2.6 多版本兼容 jar 包

Java 9 中支持在同一个 JAR 中维护不同版本的 Java 类和资源。

2.2.7 增强了 Stream,Optional,Process API2.2.8 新增 HTTP2 Client2.2.9 增强 Javadoc,增加了 HTML 5 文档的输出,并且增加了搜索功能2.2.10 增强 @Deprecated

对 Deprecated 新增了 since 和 forRemoval 属性

2.2.11 增强了钻石操作符 "<>",可以在 匿名内部类中使用了。

在 Java 9 之前,内部匿名类需要指定泛型类型,如下:

Handler<? extends Number> intHandler1 = new Handler<Number>(2) {}复制代码

而在 Java 9 中,可以自动做类型推导,如下:

Handler<? extends Number> intHandler1 = new Handler<>(2) {}复制代码
2.2.12 多分辨率图像 API:定义多分辨率图像API,开发者可以很容易的操作和展示不同分辨率的图像了。2.2.13 改进的 CompletableFuture API

CompletableFuture 类的异步机制可以在 ProcessHandle.onExit 方法退出时执行操作。 其他一些改进可参照:

docs.oracle.com/javase/9/wh…

2.3 Java SE 102.3.1 新增局部类型推断 var

var a = "aa";System.out.println(a);复制代码

var 关键字目前只能用于局部变量以及 for 循环变量声明中。

2.3.2 删除工具 javah

从JDK中移除了 javah 工具,使用 javac -h 代替。

2.3.3 统一的垃圾回收接口,改进了 GC 和其他内务管理

其他特性

ThreadLocal 握手交互 JDK 10 引入一种在线程上执行回调的新方法,很方便的停止单个线程而不是停止全部线程或者一个都不停。

基于Java的实验性JIT编译器 Java 10 开启了 Java JIT编译器 Graal,用作Linux / x64平台上的实验性JIT编译器。

提供默认的 CA 根证书

将 JDK 生态整合到单个仓库 此JEP的主要目标是执行一些内存管理,并将JDK生态的众多存储库组合到一个存储库中。

其他一些改进可以参照:

2.4 Java SE 112.4.1 Lambda 中使用 var

(var x, var y) -> x.process(y)复制代码
2.4.2 字符串 API 增强

Java 11 新增了 一系列字符串处理方法,例如:

// 判断字符串是否为空白" ".isBlank(); " Javastack ".stripTrailing();  // " Javastack"" Javastack ".stripLeading();   // "Javastack "复制代码
2.4.3 标准化 HttpClient API2.4.4 java 直接编译并运行,省去先 javac 编译生成 class 再运行的步骤2.4.5 增加对 TLS 1.3 的支持2.4.6 添加了新的垃圾收集器ZGC,不过只是实验性引入

Z垃圾收集器(也称为ZGC)是可伸缩的低延迟垃圾收集器(JEP 333)。它旨在满足以下目标:

暂停时间不超过10毫秒暂停时间不会随着堆或活动集的大小而增加处理大小从几百兆字节到几兆兆字节的堆

ZGC的核心是并发的垃圾收集器,这意味着在Java线程继续执行的同时,所有繁重的工作(标记,压缩,参考处理,字符串表清理等)都已完成。这极大地限制了垃圾回收对应用程序响应时间的负面影响。 ZGC的实验版本具有以下限制:

仅在Linux / x64上可用。不支持使用压缩的oop和/或压缩的类点。在-XX:+UseCompressedOops和-XX:+UseCompressedClassPointers选项默认为禁用。启用它们将无效。不支持类卸载。在-XX:+ClassUnloading和-XX:+ClassUnloadingWithConcurrentMark选项默认为禁用。启用它们将无效。不支持将ZGC与Graal结合使用。

其他一些改进可以参照:

2.5 Java SE 122.5.1 switch 表达式

Java 12 以后,switch 不仅可以作为语句,也可以作为表达式。

private String switchTest(int i) {    return switch (i) {        case 1 -> "1";        default -> "0";    };}复制代码

其他一些改进可以参照:

2.6 Java SE 132.6.1 使用归档方式启动

在使用归档文件启动时,JVM 将归档文件映射到其对应的内存中,其中包含所需的大多数类,而需要使用多么复杂的类加载机制。甚至可以在并发运行的 JVM 实例之间共享内存区域,通过这种方式可以释放需要在每个 JVM 实例中创建相同信息时浪费的内存,从而节省了内存空间。 在 Java 12 中,默认开启了对 JDK 自带 JAR 包类的存档,如果想关闭对自带类库的存档,可以在启动参数中加上:

-Xshare:off复制代码

而在 Java 13 中,可以不用提供归档类列表,而是通过更简洁的方式来创建包含应用程序类的归档。具体可以使用参数 -XX:ArchiveClassesAtExit 来控制应用程序在退出时生成存档,也可以使用 -XX:SharedArchiveFile 来使用动态存档功能,详细使用见如下示例。 创建存档文件示例

$ java -XX:ArchiveClassesAtExit=helloworld.jsa -cp helloworld.jar Hello复制代码

使用存档文件示例

$ java -XX:SharedArchiveFile=hello.jsa -cp helloworld.jar Hello复制代码

上述就是在 Java 应用程序执行结束时动态进行类归档,并且在 Java 10 的基础上,将多条命令进行了简化,可以更加方便地使用类归档功能。

2.6.2 Switch 表达式扩展(预览功能)

在 Java 12 中引入了 Switch 表达式作为预览特性,而在 Java 13 中对 Switch 表达式做了增强改进,在块中引入了 yield 语句来返回值,而不是使用 break。这意味着,Switch 表达式(返回值)应该使用 yield,而 Switch 语句(不返回值)应该使用 break,而在此之前,想要在 Switch 中返回内容,还是比较麻烦的,只不过目前还处于预览状态。 在 Java 13 之后,Switch 表达式中就多了一个关键字用于跳出 Switch 块的关键字 yield,主要用于返回一个值,它和 return 的区别在于:return 会直接跳出当前循环或者方法,而 yield 只会跳出当前 Switch 块,同时在使用 yield 时,需要有 default 条件。 在 Java 12 之前,传统 Switch 语句写法为:

传统形式

private static String getText(int number) {    String result = "";    switch (number) {        case 1, 2:        result = "one or two";        break;        case 3:        result = "three";        break;        case 4, 5, 6:        result = "four or five or six";        break;        default:        result = "unknown";        break;    };    return result;}复制代码

在 Java 12 之后,关于 Switch 表达式的写法改进为如下:

标签简化形式

private static String getText(int number) {    String result = switch (number) {        case 1, 2 -> "one or two";        case 3 -> "three";        case 4, 5, 6 -> "four or five or six";        default -> "unknown";    };    return result;}复制代码

而在 Java 13 中,,value break 语句不再被编译,而是用 yield 来进行值返回,上述写法被改为如下写法: yield 返回值形式

private static String getText(int number) {    return switch (number) {        case 1, 2:            yield "one or two";        case 3:            yield "three";        case 4, 5, 6:            yield "four or five or six";        default:            yield "unknown";    };}复制代码
2.6.3 文本块(预览功能)

一直以来,Java 语言在定义字符串的方式是有限的,字符串需要以双引号开头,以双引号结尾,这导致字符串不能够多行使用,而是需要通过换行转义或者换行连接符等方式来变通支持多行,但这样会增加编辑工作量,同时也会导致所在代码段难以阅读、难以维护。

Java 13 引入了文本块来解决多行文本的问题,文本块以三重双引号开头,并以同样的以三重双引号结尾终止,它们之间的任何内容都被解释为字符串的一部分,包括换行符,避免了对大多数转义序列的需要,并且它仍然是普通的 java.lang.String 对象,文本块可以在 Java 中可以使用字符串文字的任何地方使用,而与编译后的代码没有区别,还增强了 Java 程序中的字符串可读性。并且通过这种方式,可以更直观地表示字符串,可以支持跨越多行,而且不会出现转义的视觉混乱,将可以广泛提高 Java 类程序的可读性和可写性。

在 Java 13 之前,多行字符串写法为:

多行字符串写法

String html ="<html>\n" +              "   <body>\n" +              "      <p>Hello, World</p>\n" +              "   </body>\n" +              "</html>\n";                 String json ="{\n" +              "   \"name\":\"mkyong\",\n" +              "   \"age\":38\n" +              "}\n";复制代码

在 Java 13 引入文本块之后,写法为:

多行文本块写法

String html = """                <html>                    <body>                        <p>Hello, World</p>                    </body>                </html>                """;  String json = """                {                    "name":"mkyong",                    "age":38                }                """;复制代码

文本块是作为预览功能引入到 Java 13 中的,这意味着它们不包含在相关的 Java 语言规范中,这样做的好处是方便用户测试功能并提供反馈,后续更新可以根据反馈来改进功能,或者必要时甚至删除该功能,如果该功能立即成为 Java SE

准的一部分,则进行更改将变得更加困难。重要的是要意识到预览功能不是 beta 形式。

由于预览功能不是规范的一部分,因此有必要为编译和运行时明确启用它们。需要使用下面两个命令行参数来启用预览功能:

清单 8. 启用预览功能

$ javac --enable-preview --release 13 Example.java$ java --enable-preview Example复制代码
2.7 Java SE 142.7.1 instanceof模式匹配

通常情况下我们使用instanceof来探测类的真实类型,如果符合该类型,则可进行强制转换。

在Java14之前,我们通常的写法如下:

Object obj = "程序新视界";if(obj instanceof String){	String str = (String) obj;	System.out.println("关注公众号:" + str);}复制代码

通过java14的新特性,我们可以简化成如下方式:

Object obj = "程序新视界";if(obj instanceof String str){	System.out.println("关注公众号:" + str);}复制代码

我们可以通过模式匹配简洁地表达对象,并允许各种语句和表达式对其进行测试。

2.7.2 记录类型(Record Type)的引入

Java 14中记录类型(Record Type)作为预览特性被引入。记录对象允许使用紧凑的语法来声明类,和枚举类型一样,记录也是类的一种受限形式。

在idea 2020.1中,创建Record与创建类和枚举一样,可以在创建时直接选择对应的类型。

定义一个Record类型如下:

public record Point(int x, int y) {}复制代码

使用Record操作如下:

Point point = new Point(1,3);System.out.println(point.x());System.out.println(point.y());复制代码

对Record类进行反编译我们会看到如下内容:

public final class Point extends java.lang.Record {    private final int x;    private final int y;    public Point(int x, int y) { /* compiled code */ }    public java.lang.String toString() { /* compiled code */ }    public final int hashCode() { /* compiled code */ }    public final boolean equals(java.lang.Object o) { /* compiled code */ }    public int x() { /* compiled code */ }    public int y() { /* compiled code */ }}复制代码

文章来源:

标签: #jsa标签