JDK新特性(JDK9~JDK21)

一、java9新特性

java9新增了很多特性,我们针对较为突出的便于理解的特性进行说明。除了下面罗列出的新特性之外还有一些其他的内容,这些内容有的不重要或者使用较少,所以没有罗列出来。

1.1 接口私有方法

jdk9中新增了接口私有方法,即我们可以在接口中声明private修饰的方法了,这样的话,接口越来越像抽象类

public interface MyInterface {
    //抽象方法
    void eat();
    //默认方法
    default void play() {
        System.out.println("play");
    }
    //静态方法
    static void sleep() {
        System.out.println("sleep");
    }
    //私有方法 jdk9新增
    private void run() {
        System.out.println("run");
    }
}

1.2 改进的try with resource

java7中新增了try with resource语法用来自动关闭资源文件,在IO流和jdbc部分使用的比较多。使用方式是将需要自动关闭的资源对象的创建放到try后面的小括号中,在jdk9中我们可以将这些资源对象的创建代码放到小括号外面,然后将需要关闭的对象名放到try后面的小括号中即可

示例:

/*
 *  改进了try-with-resources语句,可以在try外进行初始化,
 *  在括号内填写引用名,即可实现资源自动关闭
 */
public class TryWithResource {
    public static void main(String[] args) throws FileNotFoundException {
        //jdk8以前
        try (FileInputStream fileInputStream = new FileInputStream("");
             FileOutputStream fileOutputStream = new FileOutputStream("")) {

        } catch (IOException e) {
            e.printStackTrace();
        }

        //jdk9
        FileInputStream fis = new FileInputStream("");
        FileOutputStream fos = new FileOutputStream("");
        //多资源用分号隔开
        try (fis; fos) {
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1.3 不能使用下划线命名变量

下面语句在jdk9之前可以正常编译通过,但是在jdk9(含)之后编译报错,在后面的版本中会将下划线作为关键字来使用

public class StringTest {
    public static void main(String[] args) {
        String _ = "";
    }
}

1.4 String字符串的变化

写程序的时候会经常用到String字符串,在以前的版本中String内部使用了char数组存储,对于使用英语的人来说,一个字符用一个字节就能存储,使用char存储字符会浪费一半的内存空间,因此在jdk9中将String内部的char数组改成了byte数组,这样就节省了一半的内存占用。

public class StringTest {
    public static void main(String[] args) {
        char c = 'a';//2个字节
        byte b = 97;//1个字节
    }
}

String中增加了下面2个成员变量

  • COMPACT_STRINGS:判断是否压缩,默认是true,若为false,则不压缩,使用UTF16编码。
  • coder用来区分使用的字符编码,分别为LATIN1(值为0)和UTF16(值为1)。

byte数组如何存储中文呢?通过源码(StringUTF16类中的toBytes方法)得知,在使用中文字符串时,1个中文会被存储到byte数组中的两个元素上,即存储1个中文,byte数组长度为2,存储2个中文,byte数组长度为4
以如下代码为例进行分析:

public class StringTest {
    public static void main(String[] args) {
        String str = "好"
    }
}

好对应的Unicode码二进制为0101100101111101,分别取出高8位和低8位,放入到byte数组中{01011001,01111101},这样就利用byte数组的2个元素保存了1个中文。

当字符串中存储了中英混合的内容时,1个英文字符会占用2byte数组位置,例如下面代码底层byte数组的长度为16

public class StringTest {
    public static void main(String[] args) {
        String str = "猴子monkey";
    }
}

在获取字符串长度时,若存储的内容存在中文,是不能直接获取byte数组的长度作为字符串长度的,String源码中有向右移动1位的操作(即除以2),这样才能获取正确的字符串长度。

1.5 @Deprecated注解的变化

该注解用于标识废弃的内容,在jdk9中新增了2个内容:

  • since():标识是从哪个版本开始废弃
  • forRemoval():标识该废弃的内容会在未来的某个版本中移除

1.6 模块化

java8中有个非常重要的包rt.jar,里面涵盖了java提供的类文件,在程序员运行java程序时jvm会加载rt.jar。这里有个问题是rt.jar中的某些文件我们是不会使用的,比如使用java开发服务器端程序的时候通常用不到图形化界面的库java.awt),这就造成了内存的浪费。

java9中将rt.jar分成了不同的模块,一个模块下可以包含多个包,模块之间存在着依赖关系,其中java.base模块是基础模块,不依赖其他模块。上面提到的java.awt被放到了其他模块下,这样在不使用这个模块的时候就无需让jvm加载,减少内存浪费。让jvm加载程序的必要模块,并非全部模块,达到了瘦身效果。

jar包中含有.class文件,配置文件。jmod除了包含这两个文件之外,还有native librarylegal licenses等,两者主要的区别是jmod主要用在编译期和链接期,并非运行期,因此对于很多开发者来说,在运行期仍然需要使用jar包。

模块化的优点:

  • 精简jvm加载的class类,提升加载速度
  • 对包更精细的控制,提高安全

模块与包类似,只不过一个模块下可以包含多个包。下面举例来看下

项目----公司
模块----部门:开发部,测试部
包名----小组:开发1组,开发2组
类 ----员工:张三,李四

接下来演示在测试部模块中调用开发部模块里面的类。

  1. 创建项目,项目下分别创建开发部(命名develop),测试部(命名test)2个模块

  2. 在开发部中创建下面2个包和类

    //dev1包
    package com.test.dev1;
    public class Cat {
        public void eat() {
            System.out.println("吃鱼");
        }
    }
    
    //dev2包
    package com.test.dev2;
    public class Apple {
        private String name;
    }
    
  3. 通过module-info.java的文件来描述模块,开发部模块要将com.test.dev1包导出,在src目录下创建module-info.java

    module develop {//develop名字跟模块名一致
        exports com.test.dev1;//导出的包,该包下的类可以被其他模块访问
        opens com.test.dev2;//导出包,该包下的类可以被其他模块通过反射访问
    }
    
  4. 在测试部模块的src目录下创建module-info.java

    module test {//test名字跟模块名字一致
        requires develop;//导入develop模块
    }
    
  5. 在测试部模块中创建

    package com.test.test1;
    import com.test.dev1.Cat;
    
    public class CatTest {
        public static void main(String[] args) throws Exception {
            Cat cat = new Cat();
            cat.eat();
            //反射
            Class<?> clazz = Class.forName("com.test.dev2.Apple");
            Object obj = clazz.getDeclaredConstructor().newInstance();
        }
    }
    

上面例子演示了不同模块下类的使用,主要是依赖module-info.java文件描述了模块的关系。

1.7 jshell

在一些编程语言中,例如:pythonRuby等,都提供了REPLRead Eval Print Loop 简单的交互式编程环境)。jshell就是java语言平台中的REPL

有的时候我们只是想写一段简单的代码,例如HelloWorld,按照以前的方式,还需要自己创建java文件,创建class,编写main方法,但实际上里面的代码其实就是一个打印语句,此时还是比较麻烦的。在jdk9中新增了jshell工具,可以帮助我们快速的运行一些简单的代码。

从命令提示符里面输入jshell,进入到jshell之后输入:

如果要退出jshell的话,输入/exit即可。

1.8 集合工厂方法的不可变集合

JDK9为集合API添加了一些新的工厂方法,用于创建不可变集合。这些方法包括List.of()Set.of()Map.of()等。

public class Test {
    public static void main(String[] args) {
        List<String> list = List.of("a", "b", "c");  
        Set<String> set = Set.of("a", "b", "c");  
        Map<String, Integer> map = Map.of("one", 1, "two", 2, "three", 3);
    }
}

1.9 CompleteFuture增强

JDK9CompleteFuture增加了一些方法。例如:orTimeoutcompleteOnTimeout等。

具体可参考:Java异步执行器CompletableFuture(Jdk9)改进

二、java10新特性

2.1 局部变量类型推断

jdk10以前声明变量的时候,我们会像下面这样:

public class Test {
    public static void main(String[] args) {
        String oldName = "jack";
        int oldAge = 10;
        long oldMoney = 88888888L;
        Object oldObj = new Object();
    }
}

上面我们声明的时候使用了4种不同类型的变量,在jdk10中前面的类型都可以使用var来代替,JVM会自动推断该变量是什么类型的,例如可以这样写:

public class Test {
    public static void main(String[] args) {
        var newName = "jack";
        var newAge = 10;
        var newMoney = 88888888L;
        var newObj = new Object();
    }
}

注意:

当然这个var的使用是有限制的,仅适用于局部变量,增强for循环的索引,
以及普通for循环的本地变量;它不能使用于方法形参,构造方法形参,方法返回类型等。

除了上面的新特性之外,还对jvm进行了一些优化,这里就不罗列了。

三、java11新特性

3.1 直接运行

在以前的版本中,我们需要先编译,然后再运行java程序,现在我们可以直接运行java程序,不需要先编译。

javac Test.java
java Test

现在可以直接运行:

java Test.java

3.2 String增强

strip方法,可以去除首尾空格,与之前的trim的区别是还可以去除unicode编码的空白字符,例如:

public class Test {
    public static void main(String[] args) {
        char c = '\u2000';//Unicdoe空白字符
        String str = c + "abc" + c;
        System.out.println(str.strip());
        System.out.println(str.trim());

        System.out.println(str.stripLeading());//去除前面的空格
        System.out.println(str.stripTrailing());//去除后面的空格
    }
}

isBlank方法,判断字符串长度是否为0,或者是否是空格,制表符等其他空白字符

public class Test {
    public static void main(String[] args) {
        String str = " ";
        System.out.println(str.isBlank());
    }
}

repeat方法,字符串重复的次数

public class Test {
    public static void main(String[] args) {
        String str = "monkey";
        System.out.println(str.repeat(4));
    }
}

3.3 lambda表达式中的变量类型推断

jdk11中允许在lambda表达式的参数中使用var修饰

函数式接口:

@FunctionalInterface
public interface MyInterface {
    void m1(String a, int b);
}

测试类:

//支持lambda表达式参数中使用var
MyInterface mi = (var a, var b) -> {
    System.out.println(a);
    System.out.println(b);
};

mi.m1("monkey",1024);

四、java12新特性

4.1 升级的switch语句

jdk12之前的switch语句中,如果没有写break,则会出现case穿透现象,下面是对case穿透的一个应用,根据输入的月份打印相应的季节。

public class SwitchTest {
    public static void main(String[] args) {
        int month = 3;
        switch (month) {
            case 3:
            case 4:
            case 5:
                System.out.println("spring");
            break;
            case 6:
            case 7:
            case 8:
                System.out.println("summer");
            break;
            case 9:
            case 10:
            case 11:
                System.out.println("autumn");
            break;
            case 12:
            case 1:
            case 2:
                System.out.println("winter");
            break;
            default:
                System.out.println("wrong");
            break;
        }
    }
}

jdk12之后我们可以省略全部的break和部分case,这样使用

public class SwitchTest {
    public static void main(String[] args) {
        int month = 3;
        switch (month) {
            case 3, 4, 5 -> System.out.println("spring");
            case 6, 7, 8 -> System.out.println("summer");
            case 9, 10, 11 -> System.out.println("autumn");
            case 12, 1, 2 -> System.out.println("winter");
            default -> System.out.println("wrong");
        }
    }
}

这个是预览功能,如果需要编译和运行的话需要使用下面命令,预览功能在2个版本之后会成为正式版,即如果你使用的是jdk14以上的版本,正常的编译和运行即可。否则需要使用预览功能来编译和运行

编译:

javac --enable-preview -source 12 Test.java

运行:
java --enable-preview Test

五、java13新特性

5.1 升级的switch语句

jdk13中对switch语句又进行了升级,可以switch的获取返回值

示例:

public class SwitchTest {
    public static void main(String[] args) {
        int month = 3;
        String result = switch (month) {
            case 3, 4, 5 -> "spring";
            case 6, 7, 8 -> "summer";
            case 9, 10, 11 -> "autumn";
            case 12, 1, 2 -> "winter";
            default -> "wrong";
        };
        System.out.println(result);
    }
}

对于jdk15之后的版本可以直接编译和运行,否则需要使用下面命令执行该预览功能

编译:
    javac --enable-preview -source 13 Test.java
运行:
    java --enable-preview Test

5.2 文本块的变化

jdk13之前的版本中如果输入的字符串中有换行的话,需要添加换行符

public class StringTest {
    public static void main(String[] args) {
        String s1 = "窗前明月光\n疑是地上霜\n举头望明月\n低头思故乡";
        System.out.println(s1);
    }
}

jdk13之后可以直接这样写:

public class StringTest {
    public static void main(String[] args) {
        String s2 = """
                窗前明月光
                疑是地上霜
                举头望明月
                低头思故乡
                """;
        System.out.println(s2);
    }
}

这样的字符串更加一目了然。

六、java14新特性

java14新增了很多特性,我们针对较为突出的特性进行说明。jdk12jdk13中预览版的switch特性,在jdk14中已经是正式的语法了。

6.1 instanceof模式匹配

该特性可以减少强制类型转换的操作,简化了代码,代码示例:

public class InstanceofTest {
    public static void main(String[] args) {
        //jdk14之前的写法
        Object obj = new Integer(1);
        if (obj instanceof Integer) {
            Integer i = (Integer) obj;
            int result = i + 10;
            System.out.println(i);
        }

        //jdk14新特性  不用再强制转换了
        //这里相当于是将obj强制为Integer之后赋值给i了
        if (obj instanceof Integer i) {
            int result = i + 10;
            System.out.println(i);
        } else {
            //作用域问题,这里是无法访问i的
        }
    }
}

这个是预览版的功能所以需要使用下面命令编译和运行

编译:
    javac --enable-preview -source 14 TestInstanceof.java
运行:
    java --enable-preview TestInstanceof

6.2 友好的空指针(NullPointerException)提示

jdk14中添加了对于空指针异常友好的提示,便于开发者快速定位空指针的对象。

示例代码:

class Machine {
    public void start() {
        System.out.println("启动");
    }
}

class Engine {
    public Machine machine;
}

class Car {
    public Engine engine;
}

public class TestNull {
    public static void main(String[] args) {
        //这里会报出空指针,但是哪个对象是null呢?
        new Car().engine.machine.start();
    }
}

我们在运行上面代码的时候,错误信息就可以明确的指出那个对象为null了。

此外,还可以使用下面参数来查看:

java -XX:+ShowCodeDetailsInExceptionMessages TestNull

这样编译器会明确的告诉开发者哪个对象是null。

6.3 record类型

之前在编写javabean类的时候,需要编写成员变量,get方法,构造方法,toString方法,hashcode方法,equals方法。这些方法通常会通过开发工具来生成,在jdk14中新增了record类型,通过该类型可以省去这些代码的编写。

jdk14编写User

public record User(String name, Integer age) {
}

通过反编译命令可以看到该字节码文件中的内容,User类是继承了Record类型:

javap -p -private user

编写测试类:

public class UserTest {
    public static void main(String[] args) {
        User u = new User("jack", 15);
        System.out.println(u);
        System.out.println(u.name());
    }
}

这个是预览版的功能所以需要使用下面命令编译和运行

编译:
javac --enable-preview -source 14 TestUser.java
运行:
java --enable-preview TestUser

记录类型有自动生成的成员,包括:

  • 状态描述中的每个组件都有对应的private final字段。
  • 状态描述中的每个组件都有对应的public访问方法。方法的名称与组件名称相同。
  • 一个包含全部组件的公开构造器,用来初始化对应组件。
  • 实现了equals()和hashCode()方法。equals()要求全部组件都必须相等。
  • 实现了toString(),输出全部组件的信息。

七、java15新特性

java15中更新了一些新的内容,这里仅列出对于写代码方面的新特性。

7.1 Sealed Classes

密封类和接口,作用是限制一个类可以由哪些子类继承或者实现。

  1. 如果指定模块的话,sealed class和其子类必须在同一个模块下。如果没有指定模块,则需要在同一个包下。
  2. sealed class指定的子类必须直接继承该sealed class
  3. sealed class的子类要用final修饰。
  4. sealed class的子类如果不想用final修饰的话,可以将子类声明为sealed class
//Animal类,在指定允许继承的子类时可以使用全限定名
public sealed class Animal permits Dog, Cat {
    public void eat() {}
}
//Cat类,继承Animal类
public final class Cat extends Animal {
    public void eat() {
        System.out.println("1");
    }
}
//Dog类,继承Animal类 
public final class Dog extends Animal permits Husky {
}

public final class Husky extends Dog {
}

public class Test {
    public static void main(String[] args) {
        Cat c = new Cat();
        c.eat();
        Dog d = new Dog();
    }
}

7.2 CharSequence新增的方法

该接口中新增了default方法isEmpty(),作用是判断CharSequence是否为空。

7.3 TreeMap新增方法

  • putIfAbsent
  • computeIfAbsent
  • computeIfPresent
  • compute
  • merge

7.4 文本块

文本块由预览版变为正式版

7.5 无需配置环境变量

win系统中安装完成之后会自动将java.exe,javaw.exe,javac.exe,jshell.exe这几个命令添加到环境变量中。这部分可以打开环境变量看下。

八、java16新特性

这里只介绍一些跟开发关联度较大的特性,除此之外JDK16还更新了许多其他新特性,感兴趣的同学可以去Oracle官网查看

8.1 包装类构造方法的警告

使用包装类的构造方法在编译的时候会出现警告,不建议再使用包装类的构造方法。下面代码在javac编译之后会出现警告。

Integer i = new Integer(8);

不建议使用包装类作为锁对象,倘若使用包装类作为锁对象,在编译时会出现警告。

Integer i = 8;
synchronized(i) {
}

8.2 新增日时段

DateTimeFormatter.ofPattern传入B可以获取现在时间对应的日时段,上午,下午等

public class Test {
    public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss B");
        String format = now.format(formatter);
        System.out.println(format);
    }
}

8.3 InvocationHandler新增方法

在该接口中添加了下面方法

public static Object invokeDefault(Object proxy, Method method, Object... args);

该方法可以调用父接口中default方法,比如有下面接口

interface Girl {
    default void eat() {
        System.out.println("cucumber");
    }
}

实现类

public class Lucy implements Girl {
    public void eat() {
        System.out.println("banana");
    }
}

测试类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
        Girl girl = new Lucy();
        //不使用invokeDefault会调用重写的eat方法
        Girl proxy1 = (Girl) Proxy.newProxyInstance(girl.getClass().getClassLoader(),
                girl.getClass().getInterfaces(),
                (obj, method, params) -> {
                    Object invoke = method.invoke(girl);
                    return invoke;
                });
        proxy1.eat();

        //使用invokeDefault会调用父接口中的default方法
        Girl proxy2 = (Girl) Proxy.newProxyInstance(Girl.class.getClassLoader(),
                new Class<?>[]{Girl.class},
                (obj, method, params) -> {
                    if (method.isDefault()) {
                        return InvocationHandler.invokeDefault(obj, method, params);
                    }
                    return null;
                });
        proxy2.eat();
    }
}

其他

在之前jdk版本中作为预览功能的Record类,模式匹配的instanceof,打包工具jpackage,已成为正式版。jdk16GCjvm运行时内存等内容有一些变化,例如:ZGC并发栈处理,弹性metaspace

九、java17新特性

java17是一个LTSlong term support)长期支持的版本,根据计划来看java17会支持到2029年(java8会支持到2030年,OMG),同时Oracle提议下一个LTS版本是java21,在20239月发布,这样讲LST版本的发布周期由之前的3年变为了2年。这里只介绍一些跟开发关联度较大的特性,除此之外JDK17还更新了一些其他新特性,感兴趣的同学可以从这里查看:

https://www.oracle.com/news/announcement/oracle-releases-java-17-2021-09-14/

9.1 switch语法的变化(预览)

在之前版本中新增的instanceof模式匹配的特性在switch中也支持了,即我们可以在switch中减少强转的操作。比如下面的代码:

RabbitBird均实现了Animal接口

public interface Animal {
}

public class Rabbit implements Animal {
    //特有的方法
    public void run() {
        System.out.println("rabbit run");
    }
}

public class Bird implements Animal {
    //特有的方法
    public void fly() {
        System.out.println("bird fly");
    }
}

新特性可以减少Animal强转操作代码的编写:

public class SwitchTest {
    public static void main(String[] args) {
        Animal a = new Rabbit();
        animalEat(a);
    }

    public static void animalEat(Animal a) {
        switch (a) {
            //如果a是Rabbit类型,则在强转之后赋值给r,然后再调用其特有的run方法
            case Rabbit r -> r.run();
            //如果a是Bird类型,则在强转之后赋值给b,然后调用其特有的fly方法
            case Bird b -> b.fly();
            //支持null的判断
            case null -> System.out.println("null");
            default -> System.out.println("no animal");
        }
    }
}

该功能在java17中是预览的,编译和运行需要加上额外的参数:

javac --enable-preview -source 17 SwitchTest.java
java  --enable-preview SwitchTest

9.2 Sealed Classes

jdk15中已经添加了Sealed Classes,只不过当时是作为预览版,经历了2个版本之后,在jdk17Sealed Classes已经成为正式版了。Sealed Classes的作用是可以限制一个类或者接口可以由哪些子类继承或者实现。

9.3 伪随机数的变化

增加了伪随机数相关的类和接口来让开发者使用stream流进行操作

  • RandomGenerator
  • RandomGeneratorFactory

之前的java.util.Randomjava.util.concurrent.ThreadLocalRandom都是RandomGenerator接口的实现类。

9.4 去除了AOT和JIT

  • AOT(Ahead-of-Time)是java9中新增的功能,可以先将应用中中的字节码编译成机器码。
  • Graal编译器作为使用java开发的JITjust-in-time)即时编译器在java10中加入(注意这里的JIT不是之前java中的JIT,在JEP317中有说明https://openjdk.java.net/jeps/317)。

以上两项功能由于使用量较少,且需要花费很多精力来维护,因此在java17中被移除了。当然你可以通过Graal VM来继续使用这些功能。

十、java18新特性

这里只介绍一些跟开发关联度较大的特性,除此之外JDK18还更新了许多其他新特性,感兴趣的同学可以去Oracle官网查看:https://www.oracle.com/java/technologies/javase/18-relnote-issues.html#NewFeature

10.1 默认使用UTF-8字符编码

jdk18开始,默认使用UTF-8字符编码。我们可以通过如下参数修改其他字符编码:

-Dfile.encoding=UTF-8

10.2 简单的web服务器

可以通过jwebserver命令启动jdk18中提供的静态web服务器,可以利用该工具查看一些原型,做简单的测试。在命令提示符中输入jwebserver命令后会启动,然后在浏览器中输入:http://127.0.0.1:8000,即可看到当前命令提示符路径下的文件了。

10.3 将被移除的方法

jdk18中标记了Object中的finalize方法,Thread中的stop方法将在未来被移除。

10.4 @snippet注解

以前在文档注释中编写代码时需要添加code标签,使用较为不便,通过@snippet注解可以更方便的将文档注释中的代码展示在api文档中。

/**
 * 代码注释
 * {@snippet : System.out.println();}
 */
public class Test {
    public static void main(String[] args) {
        System.out.println();
    }
}

十一、java19新特性

11.1 Virtual Threads (Preview)(虚拟线程)

虚拟线程

十二、java21新特性

java20中没有太大的变化,这里主要聊下java21的新特性,21是继java17之后,最新的LTS版本,该版本中虚拟线程称为了正式版,对虚拟线程不了解的同学可以看下之前的java19中的介绍。接下来我们看下java21中一些新特性。

12.1 字符串模板

字符串模板可以让开发者更简洁的进行字符串拼接(例如拼接sqlxmljson等)。该特性并不是为字符串拼接运算符+提供的语法糖,也并非为了替换SpringBufferStringBuilder

利用STR模板进行字符串与变量的拼接:

这个特性目前是预览版,编译和运行需要添加额外的参数:

javac --enable-preview -source 21 Test.java
java --enable-preview Test

在js中字符串进行拼接时会采用下面的字符串插值写法

let sport = "basketball"
let msg = `i like ${sport}`

看起来字符串插值写法更简洁移动,不过若在java中使用这种字符串插值的写法拼接sql,可能会出现sql注入的问题,为了防止该问题,java提供了字符串模板表达式的方式。

上面使用的STRjava中定义的模板处理器,它可以将变量的值取出,完成字符串的拼接。在每个java源文件中都引入了一个public static final修饰的STR属性,因此我们可以直接使用STRSTR通过打印STR可以知道它是java.lang.StringTemplate,是一个接口。

StringTemplate中是通过调用interpolate方法来执行的,该方法分别传入了两个参数:

  • fragements:包含字符串模板中所有的字面量,是一个List
  • values:包含字符串模板中所有的变量,是一个List

而该方法又调用了JavaTemplateAccess中的interpolate方法,经过分析可以得知,它最终是通过String中的join方法将字面量和变量进行的拼接。

其他使用示例,在STR中可以进行基本的运算(支持三元运算)

public class Test {
    public static void main(String[] args) {
        int x = 10, y = 20;
        String result = STR."\{x} + \{y} = \{x + y}";
        System.out.println(result);//10 + 20 = 30

        //调用方法:
        String result = STR."获取一个随机数: \{Math.random()}";
        System.out.println(result);

        //获取属性:
        String result = STR."int最大值是: \{Integer.MAX_VALUE}";
        System.out.println(result);

        //查看时间:
        String result = STR."现在时间: \{new SimpleDateFormat("yyyy-MM-dd").format(new Date())}";
        System.out.println(result);

        //计数操作:
        int index = 0;
        String result = STR."\{index++},\{index++},\{index++}";
        System.out.println(result);

        //获取数组数据:
        String[] cars = {"bmw","benz","audi"};
        String result = STR."\{cars[0]},\{cars[1]},\{cars[2]}";
        System.out.println(result);

        //拼接多行数据:
        String name    = "jordan";
        String phone   = "13388888888";
        String address = "北京";
        String json = STR."""
        {
            "name":    "\{name}",
            "phone":   "\{phone}",
            "address": "\{address}"
        }
        """;
        System.out.println(json);
    }
}	

自己定义字符串模板,通过StringTemplate来自定义模板

public class Test {
    public static void main(String[] args) {
        var INTER = StringTemplate.Processor.of((StringTemplate st) -> {
            StringBuilder sb = new StringBuilder();
            Iterator<String> fragIter = st.fragments().iterator();
            for (Object value : st.values()) {
                sb.append(fragIter.next());//字符串中的字面量
                sb.append(value);
            }
            sb.append(fragIter.next());
            return sb.toString();
        });

        int x = 10, y = 20;
        String s = INTER."\{x} plus \{y} equals \{x + y}";
        System.out.println(s);
    }
}

12.2 scoped values

作用域值ScopedValue

12.3 集合序列

在java.util包下新增了3个接口

  • SequencedCollection
  • SequencedSet
  • SequencedMap

通过这些接口可以为之前的部分ListSetMap的实现类增加新的方法,以List为例:

public class Test {
    public static void main(String[] args) {
        List<Integer> list = new LinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);

        List<Integer> reversedList = list.reversed();//反转List
        System.out.println(reversedList);

        list.addFirst(4);//从List前面添加元素
        list.addLast(5);//从List后面添加元素
        System.out.println(list);
    }
}

12.4 record pattern

通过该特性可以解构record类型中的值,例如

public class Test {
    public static void main(String[] args) {
        Student s = new Student(10, "jordan");
        printSum(s);
    }

    static void printSum(Object obj) {
        //这里的Student(int a, String b)就是 record pattern
        if (obj instanceof Student(int a, String b)) {
            System.out.println("id:" + a);
            System.out.println("name:" + b);
        }
    }
}

record Student(int id, String name) {
}

12.5 switch格式匹配

之前的写法

public class Test {
    public static void main(String[] args) {
        Integer i = 10;
        String str = getObjInstance(i);
        System.out.println(str);
    }

    public static String getObjInstance(Object obj) {
        String objInstance = "";
        if (obj == null) {
            objInstance = "空对象";
        } else if (obj instanceof Integer i) {
            objInstance = "Integer 对象:" + i;
        } else if (obj instanceof Double d) {
            objInstance = "Double 对象:" + d;
        } else if (obj instanceof String s) {
            objInstance = "String 对象:" + s;
        }
        return objInstance;
    }
}

新的写法,代码更加简洁

public class Test {
    public static void main(String[] args) {
        Integer i = 10;
        String str = getObjInstance(i);
        System.out.println(str);
    }

    public static String getObjInstance(Object obj) {
        return switch (obj) {
            case null -> "空对象";
            case Integer i -> "Integer 对象:" + i;
            case Double d -> "Double对象:" + d;
            case String s -> "String对象:" + s;
            default -> obj.toString();
        };
    }
}

可以在switch中使用when

public class Test {
    public static void main(String[] args) {
        yesOrNo("yes");
    }

    public static void yesOrNo(String obj) {
        switch (obj) {
            case null -> {
                System.out.println("空对象");
            }
            case String s
                    when s.equalsIgnoreCase("yes") -> {
                System.out.println("确定");
            }
            case String s
                    when s.equalsIgnoreCase("no") -> {
                System.out.println("取消");
            }
            //最后的case要写,否则编译回报错
            case String s -> {
                System.out.println("请输入yes或no");
            }
        }
    }
}

12.6 Unnamed Classes and Instance Main Methods

对于初学者来说,写的第一个HelloWorld代码有太多的概念,为了方便初学者快速编写第一段java代码,这里提出了无名类和实例main方法,下面代码可以直接运行编译,相当于是少了类的定义,main方法的修饰符和形参也省略掉了

void main() {
    System.out.println("Hello, World!");
}

12.7 Structured Concurrency

该特性主要作用是在使用虚拟线程时,可以使任务和子任务的代码编写起来可读性更强,维护性更高,更加可靠。

import java.util.concurrent.ExecutionException;
import java.util.concurrent.StructuredTaskScope;
import java.util.function.Supplier;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Food f = new Test().handle();
        System.out.println(f);
    }

    Food handle() throws ExecutionException, InterruptedException {
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            Supplier<String> yaoZi = scope.fork(() -> "新鲜大腰子烤好了");// 烤腰子的任务
            Supplier<String> drink = scope.fork(() -> "奶茶做好了");// 买饮料的任务

            scope.join() // 将2个子任务都加入
                    .throwIfFailed(); // 失败传播

            // 当两个子任务都成功后,最终才能吃上饭
            return new Food(yaoZi.get(), drink.get());
        }
    }
}

record Food(String yaoZi, String drink) {
}
posted @ 2025-06-08 15:35  夏尔_717  阅读(354)  评论(0)    收藏  举报