【高级进阶】写给大忙人看的JDK14新特性

更多学习资料,能够关注下方微信公众号,回复关键字:Java高级资料 。 便可免费获取完整的Java高级开发工程师视频课程。

在这里插入图片描述
注意html

  • IDEA 2020.1以上版本才支持JDK14

从哪几个方面学习新特性

  • 关注 语法层面(学习其余语言的语法)
  • 关注 API层面(新增 / 过期 / 废弃 )
  • 底层优化(JVM优化) 面试 / 了解底层原理

JDK版本使用者占比

根据国外网站2019年统计,使用 Java 8 的开发者比重最大,其次就是Java 11。其余版本的比重不大。这主要是因为 Java 8 和 Java 11 是 LTS 版本。其余的都是非 LTS 版本,意思就是其余版本Oracle不会一直维护。
在这里插入图片描述java

版本更新时间

在这里插入图片描述

JDK14概述

2020年3月17日,Java 14 正式GA(General Available)。这是自从Java宣布采用六个月一次的发布周期后的第五次发布。程序员

这次发布的版本包含 16 个JEP(Java Enhancement Proposals,JDK加强提案)。包括两个孵化器模块丶三个预览特性丶两个废弃功能丶两个删除功能。web

  • 孵化器模块:将还没有定稿的API和工具提早给开发者使用,以便得到反馈,并对反馈的信息整理分析后进一步改进Java的特性。
  • 预览特性:功能已经成型丶实现已经肯定,可是尚未最终定稿的功能。为了收集用户使用后真实的反馈信息,将功能提供给用户使用。促进这些功能的完善以便最终定稿。
    在这里插入图片描述

总特性概述

中文版本

JEP(Java 加强提案) 功能概述
305: instanceof的模式匹配(预览)
343: 包装工具(孵化器)
345: G1的NUMA感知内存分配
349: JFR事件流
352: 非易失性映射字节缓冲区
358: 有用的NullPointerExceptions
359: 记录(预览)
361: 开关表达式(标准)
362: 弃用Solaris和SPARC端口
363: 删除并发标记扫描(CMS)垃圾收集器
364: Mac OS上的ZGC
365: Windows上的ZGC
366: 弃用ParallelScavenge + SerialOld GC组合
367: 删除Pack200工具和API
368: 文本块(第二预览)
370: 外部存储器访问API(孵化器)

英文版本

JEP(Java 加强提案) 功能概述
305: Pattern Matching for instanceof (Preview)
343: Packaging Tool (Incubator)
345: NUMA-Aware Memory Allocation for G1
349: JFR Event Streaming
352: Non-Volatile Mapped Byte Buffers
358: Helpful NullPointerExceptions
359: Records (Preview)
361: Switch Expressions (Standard)
362: Deprecate the Solaris and SPARC Ports
363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector
364: ZGC on macOS
365: ZGC on Windows
366: Deprecate the ParallelScavenge + SerialOld GC Combination
367: Remove the Pack200 Tools and API
368: Text Blocks (Second Preview)
370: Foreign-Memory Access API (Incubator)

实用特性

JDK 14 的新特性中,对于语法层面的只有5个。因此下面将对这5个新特性进行详细讲解。面试

JEP(Java 加强提案) 功能概述
305: Pattern Matching for instanceof (Preview)
358: Helpful NullPointerExceptions
359: Records (Preview)
361: Switch Expressions (Standard)
368: Text Blocks (Second Preview)

JEP 305:instanceof的模式匹配(预览)

这个特性目前在 JDK 14 中仍是预览版本。经过为开发人员提供 模式匹配 来加强Java编程语言instanceof。模式匹配使程序中的通用逻辑(如从对象中有条件的提取组件)更加简洁安全。sql

几乎全部程序都会包含一种特殊逻辑,就是先用表达式判断是否知足某种条件。而后有条件的作进一步处理。如:instanceof表达式判断。编程

Object obj = "string";
if (obj instanceof String) {
    String s = (String) obj;
    // 使用 s 对象
    System.out.println(s);
}

这一段代码须要处理三件事情:安全

  • 判断(str 是否为String类型)
  • 转换(将obj对象转换为String)
  • 定义一个新的局部变量 s ,而后咱们就可使用这个字符串。

JEP 305写法微信

Object obj = "String";
if (obj instanceof String s) {
    // 使用 s 对象
    System.out.println(s);
} else {
    // 不可使用 s 对象
}

若是obj是一个String实例,那么它被转换为String并分配给String类型变量 s 。绑定变量在if语句的true代码块中,而不是在if语句的false代码块中。架构

与局部变量的做用范围不一样,绑定变量的做用域由包含的表达式和语句的语义决定。例以下面的代码中

Object obj = "String";
if (!(obj instanceof String s)) {
    // 报错,不可使用 s 
    System.out.println(s.trim());
} else {
    // 可使用 s
    System.out.println(s.trim());
}

// 复杂的判断条件写法
Object obj = "String";
// 因为obj instanceof String s条件成立,那么这个if域就可使用s变量,因此&&条件能够正常
if (obj instanceof String s && s.length() == 5) {
    System.out.println("obj对象是String类型,而且长度为:5");
}

// 错误的判断条件写法
Object obj = "String";
// 若是obj instanceof String s条件不成立,那么if域就不可使用s变量,s.length() == 5中不能够继续使用s变量。变量s不在||右侧的范围内。
if (obj instanceof String s || s.length() == 5) {
    System.out.println("obj对象是String类型,而且长度为:5");
}

// 类型转换和条件判断,简化代码
public boolean strNotBlank(Object obj) {
    return obj instanceof String s && s.length() > 0;
}

JEP 358:有用的NullPointerExceptions

官方描述

  • 经过精确地描述哪一个变量是null来提升由JVM生成的NullPointerException信息的可用性。

目标

  • 向开发人员和支持人员提供有关程序提早终止的有用信息。
  • 经过更清晰地将动态异常与静态程序代码关联起来,提升对程序的理解。
  • 减小新开发人员对NullPointerException的混淆和关注。

任何一个 Java 开发人员都会遇到NullPointerException(NPE)。因为NPE几乎会出如今程序中的任何位置,所以尝试捕捉和恢复它们一般都不切实际。

即便开发人员依赖JVM查明NPE的实际来源。可是对于复杂的代码,不适用调试器就没法肯定那个变量为空。假设下面的代码出现一个NPE:

Object obj = null;
System.out.println(obj.toString().trim().contains(""));

JVM将打印出致使NPE的方法丶文件名和行号:

java.lang.NullPointerException
	at com.zhichunqiu.features.java14.Features02.test(Features02.java:16)

文件名称和行号没法精准的定位到那个变量丶方法为空。objobj.toString()仍是obj.toString().trim()

若是JVM能够提供所需的信息以查明NPE的来源,而后肯定其根本缘由,而无需使用额外的工具或改组代码,则整个Java生态系统都将受益。自2006年以来,SAP的商业JVM就已经作到了这一点,得到了开发人员和支持工程师的一致好评。

JDK 14 优化JVM处理NPE

JVM NullPointerException在程序试图取消引用引用的位置处抛出(NPE)null。经过分析程序的字节码指令,JVM将精确地肯定哪一个变量是null,并在NPE 中用空细节消息描述该变量(根据源代码)。而后,null-detail消息将显示在JVM的消息中,以及方法,文件名和行号。

注意

在 JDK 14 中并无默认开启Helpful NullPointerException特性。须要本身配置JVM参数,可能之后的版本会默认开启。开启参数:-XX:+ShowCodeDetailsInExceptionMessages
在这里插入图片描述
仍是用上面的代码执行,看报错效果:

Object obj = null;
System.out.println(obj.toString().trim().contains(""));

JVM 消息将剖析该语句并经过显示致使如下内容的完整访问路径来查明缘由null

java.lang.NullPointerException: Cannot invoke "Object.toString()" because "obj" is null

	at com.zhichunqiu.features.java14.Features02.test(Features02.java:16)

JVM打印的消息除了说明NPE产生的类丶方法还说明了是obj为空致使产生了NPE。

JEP 359:Records(预览)

经过Records加强 Java编程语言。Records提供了一种紧凑的语法来声明类,这些类都是浅拷贝不可变数据。

动机和目标

早在2019年2月份,Java语言架构师Brian Goetz曾经写过一篇文章,详细的说明并吐槽了Java语言,他和许多程序员同样抱怨"Java太罗嗦太繁琐"。他提到开发人员想要建立纯数据载体类,一般都必须编写大量低价值丶重复丶容易出错的代码。好比:构造函数丶getter / setter丶equals()丶hashCode()丶toString()等方法。

官方说明以下
在这里插入图片描述
描述

Records是 Java 语言中一种新的类型声明。就像enum同样,record是一种受限制的Class形式。record的引入得到了极大程度的语言简洁性,可是也没有了类的自由度:将API与表示分离的能力。

record是包含名称和状态描述的,这个状态描述是声明record组件的。record的主体是可选的。record的定义以下:

public record Features03(String name, int age) { }

隐藏特征

因为records的声明很是简单,因此records将会默认的得到不少标准的成员变量或方法。

  • 每个组成部分(变量 )都有一个隐藏的final字段声明;

  • 每一个成员变量都有一个与变量名称相同的获取值的方法;例如:name属性的获取值方法不是instance.getName()而是instance.name()方法。这些都是编译后默认生成的。

  • 一个public的全参构造方法,构造方法的参数和顺序与类名称后面的()中声明的一致。

  • 实现了equalshashCode方法。若是两个records类型的类具备相同的类型和相同的状态,那么他们必定是相等。(其实就是说明实现的equalshashCode方法是标准的)

  • 实现了toString方法,会打印类名称和相关的属性说明。(其实就是说明实现的toString方法是标准的)

反编译后的Features03解读

// 特色1:record定义的类,默认都继承了Record,因为Java是单继承,因此record定义的类不容许在继承其余类。
// 错误示范:public record Features03 extends Object(String name, int age) { }
// 特色2:record都是final类,不容许被继承。
public final class Features03 extends java.lang.Record {
    // 特色3:全部属性都是final类型的
    private final java.lang.String name;
    private final int age;

    // 特色4:默认生成全参数构造函数
    public Features03(java.lang.String name, int age) { /* compiled code */ }

    // 特色5:默认生成toString hashCode equals方法
    public java.lang.String toString() { /* compiled code */ }

    public final int hashCode() { /* compiled code */ }

    public final boolean equals(java.lang.Object o) { /* compiled code */ }

    // 特色6:全部字段都提供getter方法,可是方法名称和属性名称一致
    public java.lang.String name() { /* compiled code */ }

    public int age() { /* compiled code */ }
}

Records的限制

  • records不能继承任何的其余类,除了头部定义的属性(字段)外,声明的任何其余属性都必须是static的。
  • records是隐式final类,不能够是abstract抽象类。
  • records的组件是隐式final。所以能够将records做为一个数据的集合,由于不用担忧数据会被修改。records只提供了属性的getter方法,属性都是final的赋值后没法修改。
  • 除了以上的限制外,records的其余行为和正常的类是同样的。具体以下
    • 能够嵌套类丶声明泛型丶实现接口丶经过new关键字实例化;
    • 能够声明静态方法丶静态字段丶静态初始化容器丶构造函数丶实例方法和嵌套类型;
// 特征1:定义泛型 / 实现接口
public record Features03<T>(String name, int age) implements Serializable {
    // 特征2:定义的成员变量,必须是静态的,能够初始化值
    private static String address;
    // 定义公共静态常量
    private static final String desc = null;

    //特征3:实例方法
    public String getDesc() {
        return null;
    }

    //特征4:静态方法
    public static String getAddress() {
        return null;
    }

    // 特征5:能够自定义构造函数
    public Features03 {
        if (age > 10) {
            throw new IllegalArgumentException("年龄大于10岁");
        }
    }

    // 嵌套的records
    record MyFeatures(String dept, String desc) {

    }
}

Features03反编译后的文件

public final class Features03 <T> extends java.lang.Record implements java.io.Serializable {
    private final java.lang.String name;
    private final int age;
    private static java.lang.String address;
    private static final java.lang.String desc;

    public Features03(java.lang.String name, int age) { /* compiled code */ }

    public java.lang.String getDesc() { /* compiled code */ }

    public static java.lang.String getAddress() { /* 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 java.lang.String name() { /* compiled code */ }

    public int age() { /* compiled code */ }

    static final class MyFeatures extends java.lang.Record {
        private final java.lang.String dept;
        private final java.lang.String desc;

        public MyFeatures(java.lang.String dept, java.lang.String desc) { /* 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 java.lang.String dept() { /* compiled code */ }

        public java.lang.String desc() { /* compiled code */ }
    }
}

JEP 361:Switch 表达式(标准)

switch表达式并非在 JDK 14才出现的,早在 JDK 12(JEP 325) 和 13(JEP 354) 中switch就做为预览语言特性出现。JDK 12 中出现了 -> 操做符,JDK 13 中出现了yield关键字。而 JDK 14 是将12 / 13的预览特性最终肯定下来做为标准特性发布。

JDK 12 以前的写法

public void test() {
    Week day = Week.FRIDAY;
    switch (day) {
        case MONDAY:
        case FRIDAY:
        case SUNDAY:
            System.out.println(6);
            break;
        case TUESDAY:
            System.out.println(7);
            break;
        case THURSDAY:
        case SATURDAY:
            System.out.println(8);
            break;
        case WEDNESDAY:
            System.out.println(9);
            break;
    }
}

// 将匹配条件后的值赋值给一个变量
public void test2() {
    int numLetters;
    Week day = Week.FRIDAY;
    switch (day) {
        case MONDAY:
        case FRIDAY:
        case SUNDAY:
            numLetters = 6;
            break;
        case TUESDAY:
            numLetters = 7;
            break;
        case THURSDAY:
        case SATURDAY:
            numLetters = 8;
            break;
        case WEDNESDAY:
            numLetters = 9;
            break;
    }
}
enum Week {
    MONDAY,
    FRIDAY,
    SUNDAY,
    TUESDAY,
    THURSDAY,
    SATURDAY,
    WEDNESDAY
}

JDK 12写法

在 JDK 12 中引入一种新形式的开关标签 ”case L ->“,以表示若是标签匹配则仅执行标签右边的代码。并且还容许使用逗号分隔多个常量。

  • ”case L ->“开关标签右侧的代码不只限于表达式,还能够是代码块{}或者throw语句。

  • switch既然在 JDK 12 中的描述是表达式,那么就说明能够将结果赋值给一个变量。

public void test1() {
    Week day = Week.FRIDAY;
    switch (day) {
        case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
        case TUESDAY -> {
            // case 后面是一个代码块,包含多行代码
            System.out.println("输入的日期为:" + TUESDAY);
            System.out.println(7);
        }
        case THURSDAY, SATURDAY -> System.out.println(8);
        case WEDNESDAY -> System.out.println(9);
        // case 后面抛出异常
        default -> throw new IllegalArgumentException("输入的星期有误");
    }
}

// 将匹配条件后的值赋值给一个变量,而后将结果输出
// 即将结果赋值给一个变量,做为表达式使用,switch须要以分号结尾
// 方式一:赋值变量后输出结果
@Test
public void test3() {
    Week day = Week.FRIDAY;
    int numLetters = switch (day) {
        case MONDAY, FRIDAY, SUNDAY -> 6;
        case TUESDAY -> 7;
        case THURSDAY, SATURDAY -> 8;
        case WEDNESDAY -> 9;
        // case 后面抛出异常
        default -> throw new IllegalArgumentException("输入的星期有误");
    };// 末尾须要以分号结束,由于这个时候switch做为表达式使用
    System.out.println(numLetters);
}
// 方式二:将switch做为表达式直接输出
public void test4() {
    Week day = Week.FRIDAY;
    System.out.println(
            // 将表达式结果输出
            switch (day) {
                case MONDAY, FRIDAY, SUNDAY -> 6;
                case TUESDAY -> 7;
                case THURSDAY, SATURDAY -> 8;
                case WEDNESDAY -> 9;
                // case 后面抛出异常
                default -> throw new IllegalArgumentException("输入的星期有误");
            }
    );
}

enum Week {
    MONDAY,
    FRIDAY,
    SUNDAY,
    TUESDAY,
    THURSDAY,
    SATURDAY,
    WEDNESDAY
}

JDK 13 写法

在 JDK 13 中为了解决 JDK 12 中switch做为表达式若是返回结果时候,case -> 右边若是是一个代码块,那么就不知道什么时候返回结果。所以引入了一个关键字:yield

  • 若是使用case -> 的写法,若是右边只有一个表达式,就不须要使用yield关键字。只有代码块{}的时候才须要使用。
  • 若是使用case : 的写法,那么不管右边如何,须要返回结果都必须使用yield
// yield关键字的用法,返回结果
// 方法一: case -> 写法
public void test5() {
    Week day = Week.FRIDAY;
    // 将表达式结果输出,
    int numLetters = switch (day) {
        case MONDAY, FRIDAY, SUNDAY -> 6;
        case TUESDAY -> 7;
        case THURSDAY, SATURDAY -> 8;
        // case后面若是是代码块,返回结果须要使用关键字:yield,不可使用return返回结果
        case WEDNESDAY -> {
            System.out.println("case -> 后面是代码块时候返回结果");
            yield 9;
        }
        // case 后面抛出异常
        default -> throw new IllegalArgumentException("输入的星期有误");
    };
}

// 方法二:case: 写法
public void test6() {
    Week day = Week.FRIDAY;
    // 将表达式结果输出
    int numLetters = switch (day) {
        case MONDAY, FRIDAY, SUNDAY: yield  6;
        case TUESDAY: yield 7;
        case THURSDAY, SATURDAY: yield 8;
        // case后面若是是代码块,返回结果须要使用关键字:yield,不可使用return返回结果
        case WEDNESDAY:  {
            System.out.println("case -> 后面是代码块时候返回结果");
            yield 9;
        }
        // case 后面抛出异常
        default:  throw new IllegalArgumentException("输入的星期有误");
    };
}

enum Week {
    MONDAY,
    FRIDAY,
    SUNDAY,
    TUESDAY,
    THURSDAY,
    SATURDAY,
    WEDNESDAY
}

警告

对于控制语句丶break丶yield丶return和continue,不能跳过switch表达式。

for (int i = 0; i < Integer.MAX_VALUE; ++i) {
    int k = switch (e) {
        case 0:
            yield 1;
        case 1:
            yield 2;
        default:
            // 错误的写法,不能跳过switch表达式
            continue z;
    };
}

JEP 368:文本块(第二次预览)

一句话含义

文本块是多行字符串文字,它能够有效避免字符串特殊字符须要转义序列。

JEP 目标

  • 经过简化跨行源代码的字符串工做,从而简化了编写Java程序的任务,同时避免了常见状况下的转义序列。
  • 加强Java程序中表示用非Java语言编写的代码字符串的可读性。
  • 经过规定相同的转义序列而且以字符串文字的形式操做,从而支持字符串文字的迁移。

病症所在

在 Java 中,在字符串文字中嵌入HTML丶XML丶SQL和JSON等片断,一般须要先进行转义和大量的串联,而后才能编译包含该片断的代码。一般这些代码都是难以维护的。

所以,经过具备一种语言学机制,能够将多行文字更直观的表示字符串,从而跨越多行也不会出现转义,从而提升了Java类程序的可读性和可写性。这就是文本块诞生的缘由。

语法规则

文本块的定义由开始定界符(三个双引号字符)和结束定界符(三个双引号字符)肯定文本块的边界String str = """ 文本内容 """。同时引入了\表示取消换行和\s表示一个空格。

文本块示例

// 注意文本块每一行末尾都有一个换行符,因此普通的字符串长度要多1个单位。
public void testBlock() {
    // 每一行字符串后面都有\n转义,可读性太差
    String html = "<html>\n" +
            "\t<body>\n" +
            "\t\t<p>Hello, world</p>\n" +
            "\t</body>\n" +
            "</html>";
    // 文本块写法,简单清晰,可读性好
    String htmlBlock = """
            <html>
            	<body>
            		<p>Hello, world</p>
            	</body>
            </html>
            """;
    System.out.println(html.length()); // 结果:53
    System.out.println(htmlBlock.length()); // 结果:54
}

语法示例

// 正确的定义文本块
String sql = """ // 开始分隔符一行
        文本块内容
        """; // 结束分隔符一行
    
// 文本块中三个"字符的序列,至少须要转义一个字符,以免和结束定界符冲突
String code =
        """
        String text = \"""
            A text block inside a text block
        \""";
        """;

// 错误语法示范
String a = """""";   // 错误,在开始分隔符后面没有换行符
String b = """ """;  // 错误,在开始分隔符后面没有换行符
String c = """
           ";        // 错误,并无结束分隔符
String d = """
           abc \ def
           """;      // 错误,斜杆没有转义

新的转义序列

为了更好的处理换行符和空格的处理,JDK 14 引入了两个新的转义序列。

  • \转义序列明确禁止插入换行符 , 因为字符文字和传统字符串文字不容许嵌入换行符的缘由,\转义序列仅适用于文本块
  • 新的\s转义序列仅转换为一个空格(\u0020),该\s转义序列能够在这两个文本块和传统的字符串文字中。
// 对于一个很长的字符串内容,为了更加方便查看,一般会拆分为较小的子字符串进行拼接,而后将结果的字符串表达式分散到几行中:
String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " +
        "elit, sed do eiusmod tempor incididunt ut labore " +
        "et dolore magna aliqua.";

// 使用\<line-terminator>转义序列,能够将其表示为:
// \ 表示后面是没有换行符的,因此字符串会是一行输出
String text = """
                Lorem ipsum dolor sit amet, consectetur adipiscing \
                elit, sed do eiusmod tempor incididunt ut labore \
                et dolore magna aliqua.\
                """;
// \s在这个示例中,在每行的末尾使用能够确保每行正好是六个字符长度
String colors = """
    red  \s
    green\s
    blue \s
    """;
    
// 能够在任何使用字符串文字的地方使用文本块。例如,文本块和字符串文字能够互换使用:
String code = "public void print(Object o) {" +
              """
                  System.out.println(Objects.toString(o));
              }
              """;
                  
// 不建议的作法:使用文本块拼接,这样会显得很笨拙
String code = """
              public void print(""" + type + """
                                                 o) {
                  System.out.println(Objects.toString(o));
              }
              """;

API对文本块的支持:附加方法

  • String::stripIndent():用于从文本块内容中去除附带的空白
  • String::translateEscapes():用于翻译转义序列
  • String::formatted(Object... args):简化文本块中的值替换
更多学习资料,能够关注下方微信公众号,回复关键字:Java高级资料 。 便可免费获取完整的Java高级开发工程师视频课程。

在这里插入图片描述