JDK17 新特性梳理及内存模型刨析

发布于:2025-04-21 ⋅ 阅读:(89) ⋅ 点赞:(0)

为什么是JDK17

“你发任你发,我用 Java8”。虽然业界现在对于 JDK8 后每半年更新一次的 JDK 新版本都保持着比较谨慎的态度,但是 JDK17是一个 Java 程序员不得不去关注的新版本。 最直观的原因是,作为现代 Java 应用基石的 Spring 和 SpringBoot ,都在新版本中走出了抛弃 JDK8,支持 JDK17 的这一步。
在这里插入图片描述
在这里插入图片描述
你或许不一定需要像技术极客一样去紧追 JDK 各种令人眼花缭乱的最新特性,但是 JDK17 却是每个 Java 程序员必须走出的下一个里程碑。不光是因为 JDK17 是 JDK8 后一个重要的 LTS 长期支持版本,更是因为在应用生态构建方面,JDK17 会比之前的 JDK11 更加成熟。

​ 个人觉得,跳过 JDK11,直接入手 JDK17,对 JDK8 时代的程序员来说是一个比较实惠的选择。而至于后续的 JDK21 版本,除了虚拟线程比较亮眼外,其他特性相比 JDK17,感觉不痛不痒。因此,接下来,楼兰将在 JDK8 的基础上,带你全面认识一下 JDK17。

语法层面新特性

​ 先从一些无关痛痒的小的语法增强带你来走进 JDK17。

1、文本块

文本块功能,文本块指多行的字符串,使用连续的三个双引号来包围一段带换行的文字,它避免了换行转义的需要,并支持String.format。

同时添加了两个新的转义字符:
, 置于行尾,用来将两行连接为一行
\s: 单个空白字符

示例代码:

String query =
        """
        SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB` \s
        WHERE `CITY` = '%s' \
        ORDER BY `EMP_ID`, `LAST_NAME`;
        """;
System.out.println("===== query start =====");
System.out.println(String.format(query, "合肥"));
System.out.println("===== query stop =====");



    打印结果:

    SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`
    WHERE `CITY` = '合肥' ORDER BY `EMP_ID`, `LAST_NAME`;

2 、Switch 表达式增强

从 JDK8 到 JDK17,Switch 表达式做了很大的增强,再也不是简单的if-else的替代品了。

​ 扩展switch语句,使其既可以作为语句使用,也可以作为表达式使用,并且两种形式都可以用“传统”或“简化”的作用域和控制流行为。同时添加了yield关键字,提供break 与switch返回值的功能。

示例 1:可以将多个匹配写到一起。

switch (name) {
   
    case "李白", "杜甫", "白居易" -> System.out.println("唐代诗人");
    case "苏轼", "辛弃疾" -> System.out.println("宋代诗人");
    default -> System.out.println("其他朝代诗人");
}

示例 2:每个分支直接返回一个值。

int tmp = switch (name) {
   
    case "李白", "杜甫", "白居易" -> 1;
    case "苏轼", "辛弃疾" -> 2;
    default ->  {
   
        System.out.println("其他朝代诗人");
        yield 3;
    }
};

3、instanceof的模式匹配

instances 增加了模式匹配的功能,如果变量类型经过instances判断能够匹配目标类型,则对应分支中无需再做类型强转。

示例代码:

if (o instanceof Integer i && i > 0) {
   
    System.out.println(i.intValue());
} else if (o instanceof String s && s.startsWith("t")) {
   
    System.out.println(s.charAt(0));
}

4、var 局部变量推导

对于某些可以直接推导出类型的局部变量,可以使用var进行声明。

var nums = new int[] {
   1, 2, 3, 4, 5};
        var sum = Arrays.stream(nums).sum();
        System.out.println("数组之和为:" + sum);

这个特性仁者见仁,智者见智。 Java 的强类型语法更能保护代码安全。

模块化及类封装

在这里插入图片描述

public class RecordTest {
   

    @Test
    public void getPoint() throws IllegalAccessException {
   
        Point p = new Point(10,20);
        for (Method method : p.getClass().getMethods()) {
   
            System.out.println(method);
        }
        for (Field field : p.getClass().getDeclaredFields()) {
   
            System.out.println(field);
           // 不允许通过反射修改值。
            // field.setAccessible(true);
            // field.set(p,30);
        }
        System.out.println(p.x()+"===="+p.y());
    }
}

record记录类的实现原理,其实大致相当于给每个属性添加了private final声明。这样就不允许修改。另外,从字节码也能看到,对于record类,同时还实现了toString,hashcode,equals方法,而这些方法都被声明成了final,进一步阻止应用定制record相关的业务逻辑。
在这里插入图片描述

2 、隐藏类 Hidden Classes

​ 从 JDK15 开始,JDK 引入了一个很有意思的特性,隐藏类。隐藏类是一种不能被其他类直接使用的类。隐藏类不再依赖于类加载器,而是通过读取目标类字节码的方式,创建一个对其他类字节码隐藏的class对象,然后通过反射的方式创建对象,调用方法。

​ 我们先来一个示例理解一下什么是隐藏类,再来思考隐藏类有什么用处。

​ 比如先编写一个普通的测试类

public class HiddenClass {
   
    public String sayHello(String name) {
   
        return "Hello, " + name;
    }

    public static void printHello(String name) {
   
        System.out.printf("""
                Hello, %s !
                Hello, HiddenClass !
                %n""", name);
    }
}

传统方式下,要使用这个类,就需要经过编译,然后类加载的整个过程。但是隐藏类机制允许直接从编译后的class字节码入手,并且绕过整个类加载的复杂过程,直接使用这个类。

​ 比如,我们可以使用下面的方法获取class字节数组:

 public void printHiddenClassBytesInBase64(){
   
        //编译后的 class 文件地址
        String classPath = "/Users/roykingw/DevCode/JDK17Demo/demoModule/target/classes/com/roy/hidden/HiddenClass.class";
        try {
   
            byte[] bytes = Files.readAllBytes(Paths.get(classPath));
            System.out.println(Base64.getEncoder().encodeToString(bytes));
        } catch (IOException e) {
   
            e.printStackTrace();
        }
    }

这样就可以拿到一串编码后的class文件的字节码。接下来,就可以用这个字节码直接生成这个类。例如:

public void testInvokeHiddenClass() throws Throwable {
   
        //class文件的字节码
        String CLASS_INFO = "yv66vgAAAD0ANgoAAgADBwAEDAAFAAYBABBqYXZhL2xhbmcvT2JqZWN0AQAGPGluaXQ+AQADKClWEgAAAAgMAAkACgEAF21ha2VDb25jYXRXaXRoQ29uc3RhbnRzAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsJAAwADQcADgwADwAQAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwgAEgEAI0hlbGxvLCAlcyAhCkhlbGxvLCBIaWRkZW5DbGFzcyAhCiVuCgAUABUHABYMABcAGAEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAZwcmludGYBADwoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9pby9QcmludFN0cmVhbTsHABoBABpjb20vcm95L2hpZGRlbi9IaWRkZW5DbGFzcwEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAcTGNvbS9yb3kvaGlkZGVuL0hpZGRlbkNsYXNzOwEACHNheUhlbGxvAQAEbmFtZQEAEkxqYXZhL2xhbmcvU3RyaW5nOwEACnByaW50SGVsbG8BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApTb3VyY2VGaWxlAQAQSGlkZGVuQ2xhc3MuamF2YQEAEEJvb3RzdHJhcE1ldGhvZHMPBgApCgAqACsHACwMAAkALQEAJGphdmEvbGFuZy9pbnZva2UvU3RyaW5nQ29uY2F0RmFjdG9yeQEAmChMamF2YS9sYW5nL2ludm9rZS9NZXRob2RIYW5kbGVzJExvb2t1cDtMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL2ludm9rZS9NZXRob2RUeXBlO0xqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9pbnZva2UvQ2FsbFNpdGU7CAAvAQAISGVsbG8sIAEBAAxJbm5lckNsYXNzZXMHADIBACVqYXZhL2xhbmcvaW52b2tlL01ldGhvZEhhbmRsZXMkTG9va3VwBwA0AQAeamF2YS9sYW5nL2ludm9rZS9NZXRob2RIYW5kbGVzAQAGTG9va3VwACEAGQACAAAAAAADAAEABQAGAAEAGwAAAC8AAQABAAAABSq3AAGxAAAAAgAcAAAABgABAAAAAwAdAAAADAABAAAABQAeAB8AAAABACAACgABABsAAAA7AAEAAgAAAAcrugAHAACwAAAAAgAcAAAABgABAAAABQAdAAAAFgACAAAABwAeAB8AAAAAAAcAIQAiAAEACQAjACQAAQAbAAAAQAAGAAEAAAASsgALEhEEvQACWQMqU7YAE1exAAAAAgAcAAAACgACAAAACQARAA0AHQAAAAwAAQAAABIAIQAiAAAAAwAlAAAAAgAmACcAAAAIAAEAKAABAC4AMAAAAAoAAQAxADMANQAZ";
        byte[] classInBytes = Base64.getDecoder().decode(CLASS_INFO);
        Class<?> proxy = MethodHandles.lookup()
                .defineHiddenClass(classInBytes, true, MethodHandles.Lookup.ClassOption.NESTMATE)
                .lookupClass();

        // 输出类名
        System.out.println(proxy.getName());
        // 输出类有哪些函数
        for (Method method : proxy.getDeclaredMethods()) {
   
            System.out.println(method.getName());
        }
        // 2. 调用对应的方法
        MethodHandle mhPrintHello = MethodHandles.lookup().findStatic(proxy, "printHello", MethodType.methodType(void.class, String.class));
        mhPrintHello.invokeExact("loulan");
        Object proxyObj = proxy.getConstructors()[0

网站公告

今日签到

点亮在社区的每一天
去签到