Java9-17新特性

一、简介

由于 SpringBoot 3 最低支持的 JDK 版本是 17 了,加上 JDK 17 是一个 LTS 版本(Long-Term Support,长期稳定可靠的版本),所以还是有必要学习下 JDK 9 - JDK17 的新特性的。写这篇文章帮助自己记忆,也方便后续查阅。

关于 JDK 各版本新特性可查看网址:https://openjdk.org/

二、新特性

下面是我认为比较重要的一些新特性,有些感觉平时不会用的语法糖就不讲了,有兴趣的可以自己去了解了解

接口私有方法(JDK9)

JEP 213: Milling Project Coin

在讲 JDK 9 新特性 —— 接口私有方法之前,我们先了解下在之前的 JDK 版本中接口的变化。

在 JDK 8 之前,我们接口里只存在常量抽象方法

public interface MyInterface {

    /**
     * 常量(默认就是public abstract final修饰的)
     */
    String CONSTANT = "常量";

    /**
     * 抽象方法(默认就是public abstract修饰的)
     */
    void abstractMethod();
}

在 JDK 8 版本里,增加了默认方法静态方法

默认方法可以定义方法的默认实现,使用 default 关键字修饰

public interface MyInterface {

    /**
     * 默认方法
     */
    default void defaultMethod() {
        System.out.println("这是默认方法");
    }
}

public class MyClass implements MyInterface{

    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        myClass.defaultMethod();
    }
}

运行结果:

这是默认方法

因为实现类可以不重写接口中的默认方法,所以可以往现存接口中添加新的方法,不用强制那些已经实现了该接口的类也同时实现这个新加的方法,非常棒!

此外还可以在接口里定义静态方法,使用 static 关键字修饰

public interface MyInterface {

    /**
     * 静态方法
     */
    static void staticMethod() {
        System.out.println("这是静态方法");
    }
}

public class MyTest{

    public static void main(String[] args) {
        MyInterface.staticMethod();
    }
}

运行结果:

这是静态方法

有的同学可能已经发现问题了,如果我多个默认方法用到了同一段代码,岂不是要写很多重复代码,于是 JDK 9 在接口里增加了私有方法,可以将相同的代码提取到私有方法里

public interface MyInterface {

    /**
     * 默认方法
     */
    default void defaultMethod() {
        System.out.println("这是默认方法");
        privateMethod();
    }

    /**
     * 默认方法2
     */
    default void defaultMethod2() {
        System.out.println("这是默认方法2");
        privateMethod();
    }

    /**
     * 私有方法
     */
    private void privateMethod() {
        System.out.println("这是私有方法");
    }
}

public class MyClass implements MyInterface{

    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        myClass.defaultMethod();
        myClass.defaultMethod2();
    }
}

运行结果:

这是默认方法
这是私有方法
这是默认方法2
这是私有方法

String存储结构的变化(JDK9)

JEP 254: Compact Strings

String 的存储结构,由 char[] 改成了 byte[],并且增加了一个 coder 属性来表示使用的字符编码

JDK 9 之前

image-20230821154138106

JDK 9 之后

image-20230821154208233

为什么要这么改呢,我们看下文档里是怎么说的

image-20230821154323311

大致的意思就是,原本的存储方式 char 是占2个字节(16位),实际应用程序中字符串又是使用最多的,而大多数的字符串只包含 Latin-1 字符,这个字符只需要1个字节的存储空间,而这种字符串相当于有一半的空间是浪费的。

改版之后,使用byte[],并增加了 coder 属性,这个 coder 有两个可能的值

image-20230821155423373

  • 0:表示字符串对象使用 Latin-1 编码(即只包含 ASCII 字符)
  • 1:表示字符串对象使用 UTF-16 编码(即可以包含任意 Unicode 字符)

对于每个字符串,会先判断它是不是只有 Latin-1 字符。如果是,就按照1字节的规格进行分配内存。如果不是,就按照2字节的规格进行分配。

这样改的话,内存的使用率就提升了,GC的次数会相应的减少。

快速创建只读集合(JDK9、10)

JEP 269: Convenience Factory Methods for Collections

从 JDK 9 开始,集合(List/ Set/ Map)都添加了 of (JDK9添加)和 copyOf (JDK10添加)方法,用来创建不可变的集合

public class MyTest {

    public static void main(String[] args) {
        List<String> list = List.of("Java", "Spring", "SpringBoot");
        System.out.println(list);

        Set<String> set = Set.of("Java", "Spring", "SpringBoot");
        System.out.println(set);

        Map<Integer, String> map = Map.of(1, "Java", 2, "Spring", 3, "SpringBoot");
        System.out.println(map);

        List<String> listCopy = List.copyOf(list);
        System.out.println(listCopy);

        Set<String> setCopy = Set.copyOf(set);
        System.out.println(setCopy);

        Map<Integer, String> mapCopy = Map.copyOf(map);
        System.out.println(mapCopy);
    }
}

运行结果:

[Java, Spring, SpringBoot]
[Spring, SpringBoot, Java]
{3=SpringBoot, 1=Java, 2=Spring}
[Java, Spring, SpringBoot]
[Spring, SpringBoot, Java]
{3=SpringBoot, 1=Java, 2=Spring}

JDK 的这个优化想法是不错的,但其实我们日常开发用到只读集合的场景并不多,但是可以按照这个思路,写一个快速创建可变集合的工具类

文本块(JDK13、14、15)

JEP 355: Text Blocks (Preview)

JEP 368: Text Blocks (Second Preview)

JEP 378: Text Blocks

JDK 13 开始预览,JDK 14 二次预览,JDK 15 成为正式版本

目的是简化多行字符串的创建和处理,在旧的 JDK 版本中,我们要创建一个多行字符串,需要用到转义字符和串联操作符来连接多个字符串,例如我们需要创建这样的字符串:

{
“name”: “张三”,
“age”: 18
}

之前我们的写法是这样的:

String text = "{\n" +
                "  \"name\": \"张三\",\n" +
                "  \"age\": 18\n" +
                "}";

而用文本块,我们只需要以三个双引号 """ 开始和结束,中间的内容就是字符串的具体内容,如下:

String text2 = """
                {
                  "name": "张三",
                  "age": 18
                }
                """;

更直观的 NullPointerException 提示(JDK14)

JEP 358: Helpful NullPointerExceptions

空指针异常是日常开发中非常常见的异常,但是有个问题,我们不知道具体什么对象是空的,尤其在一行代码上出现多个对象可能为空时,只能通过 debug 代码才能看出来

而 JDK 14 解决了这个问题,优化了 NullPointerException 的提示,可以一眼就看出哪个对象是空的

文档中举的例子还是比较直观的

image-20230822144848690

优化后的提示是类似这样的:

Exception in thread "main" java.lang.NullPointerException: 
        Cannot assign field "i" because "a" is null
    at Prog.main(Prog.java:5)

密封类(JDK15、16、17)

JEP 360: Sealed Classes (Preview)

JEP 397: Sealed Classes (Second Preview)

JEP 409: Sealed Classes

JDK 15 开始预览,JDK 16 二次预览,JDK 17 成为正式版本

在 JAVA 里我们可以通过继承来扩展现有类或者实现接口。这种扩展的灵活性使得类的行为变得难以预测,可能导致不必要的错误或安全漏洞

所以就出现了密封类,密封类可以用来限制子类的范围,增强代码的安全性和可维护性

使用 sealed 关键字在类或者接口前进行修饰,使用 permits 关键字来指定可以继承的类

子类也必须用 sealednon-sealedfinal 中的一种关键字修饰

  • sealed:用于声明密封类,只允许有限的子类继承。
  • non-sealed:用于声明普通类,可以被任何类所继承。
  • final:用于声明最终类,禁止继承。
public sealed class Animal permits Dog, Cat {
}

public final class Dog extends Animal {
}

public final class Cat extends Animal {
}

由于父类是密封类,指定了继承类是 Dog、Cat,这时候如果我写一个 Bird 类继承 Animal 就会报错

instanceof的模式匹配(JDK14、15、16)

JEP 305: Pattern Matching for instanceof (Preview)

JEP 375: Pattern Matching for instanceof (Second Preview)

JEP 394: Pattern Matching for instanceof

instanceof的模式匹配从 JDK 14 开始预览,JDK 15 二次预览,JDK 16 成为正式版本

简化了 instanceof 的书写方式

在 JDK 14 之前,我们需要先判断类型,再做向下转型

if (obj instanceof String) {
    String s = (String) obj;
    // use s
}

而 JDK 14 之后,可以将判断和转型合并成一步

if (obj instanceof String s) {
    // can use s here
}

另外,因为 instanceof 的新变量作用域只在if语句里,如果后面接 && (与操作)可以继续使用instanceof 的变量,如下:

if (obj instanceof String s && s.length() > 5) {.. s.contains(..) ..}

但是由于短路操作,所以后面接||(或操作)就不能使用 instanceof 的变量,因为有可能 instanceof 不满足新变量未定义,如下:

if (obj instanceof String s || s.length() > 5) {.. s.contains(..) ..}

image-20230822160906358

switch增强(JDK12、13、17)

JEP 325: Switch Expressions (Preview)

JEP 354: Switch Expressions (Second Preview)

JEP 361: Switch Expressions

JEP 406: Pattern Matching for switch (Preview)

  • 在 JDK 7 版本之前 switch 语句只能使用整数(byte、short、int)和字符类型(char)作为表达式,并且只支持单个 case 分支,没有 break 会穿透到下一个 case。
  • 在 JDK 7 版本里,增加了支持字符串类型(String)和枚举类型(enum)。
  • 在 JDK 12 版本里,增加了 switch 表达式,用箭头(->)代替冒号(:)
  • 在 JDK 13 版本里,增加了 yield 关键字
  • 在 JDK 17 版本里,支持 switch 类型,简化原本 instanceof 的写法,并且支持 case null,对于枚举类和密封类会进行完整性校验,从而不用写 default 分支

在 JDK 12 之前,我们是这么写的

public static void main(String[] args) {

    int month = 1;
    switch (month) {
        case 1, 2, 3, 4, 5, 6:
            System.out.println("上半年");
            break;
        case 7, 8, 9, 10, 11, 12:
            System.out.println("下半年");
            break;
        default:
            System.out.println("不存在的月份");
            break;
    }
}

为了防止穿透,我们必须要写上 break,而在 JDK 12 之后,用 switch 表达式写,可以省略 break

public static void main(String[] args) {

    int month = 1;
    switch (month) {
        case 1, 2, 3, 4, 5, 6 -> System.out.println("上半年");
        case 7, 8, 9, 10, 11, 12 -> System.out.println("下半年");
        default -> System.out.println("不存在的月份");
    }
}

如果有返回值,可以这么写

public static void main(String[] args) {

    int month = 1;
    String text = switch (month) {
        case 1, 2, 3, 4, 5, 6 -> "上半年";
        case 7, 8, 9, 10, 11, 12 -> "下半年";
        default -> "";
    };
    System.out.println(text);
}

在 JDK 13版本之后,我们也可以使用 yield 关键字来设置返回值

public static void main(String[] args) {

    int month = 1;
    String text = switch (month) {
        case 1, 2, 3, 4, 5, 6:
            yield "上半年";
        case 7, 8, 9, 10, 11, 12:
            yield "下半年";
        default:
            yield "";
    };
    System.out.println(text);
}

在 JDK 17 版本之后,对于一个 Object 向下转型时,可以摆脱 instanceof + cast 了

之前的写法

static String formatter(Object o) {
    String formatted = "unknown";
    if (o instanceof Integer i) {
        formatted = String.format("int %d", i);
    } else if (o instanceof Long l) {
        formatted = String.format("long %d", l);
    } else if (o instanceof Double d) {
        formatted = String.format("double %f", d);
    } else if (o instanceof String s) {
        formatted = String.format("String %s", s);
    }
    return formatted;
}

JDK 17 之后

static String formatterPatternSwitch(Object o) {
    return switch (o) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> o.toString();
    };
}

另外支持 case null

static void test(Object o) {
    switch (o) {
        case null     -> System.out.println("null!");
        case String s -> System.out.println("String");
        default       -> System.out.println("Something else");
    }
}

对于枚举类和密封类会进行完整性校验,从而不用写 default 分支

sealed interface S permits A, B, C {}
final class A implements S {}
final class B implements S {}
record C(int i) implements S {}  // Implicitly final

static void switchStatementComplete(S s) {
    switch (s) {    // Error - incomplete; missing clause for permitted class B!
        case A a :
            System.out.println("A");
            break;
        case C c :
            System.out.println("C");
            break;
    };
}

如果少写一个 B 的情况,编译期会报错

image-20230822141212160

枚举也是同样的道理

垃圾收回相关优化(JDK9-17)

JDK9

JDK10

JDK11

JDK12

JDK13

JDK14

JDK15

JDK16

三、总结

JDK 9-17 增加了许多新的特性,对我们的日常开发有很大帮助,JDK 11推出的 ZGC 是一款性能比 G1 还要好的垃圾回收器,JDK 17 也是史上最快的 Java 版本。

Spring Boot 3.0 的最低支持版本已经提升到 JDK 17,并且不再向下兼容 JDK 8,开发者从 JDK 8 转移到 JDK 17 将是大势所趋,我们不能畏惧新的东西,保持学习的姿态,通过阅读文档和实践,不断的进步!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每天进步亿点点的小码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值