Java 20没有发布重要的新特性,本文以Java21版本为主。
Java 21 是新的LTS版本,其中发布了众多新的特性。

字符串模板(预览版)

使用官方的STR、FMT

支持通过STRFMT来实现字符串拼接,可以自定义模板处理器组织字符串输出形式。

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

import static java.util.FormatProcessor.FMT;

/**
 * @author imyzt
 * @date 2023/12/19
 * @description 模板字符串
 */
public class TemplateString {

    public static void main(String[] args) {

        String str = "world";
        String result = STR."hello \{str}";
        System.out.println(result);
        System.out.println(STR);

        String name = "yzt";
        String[] blogAddress = {"imyzt.top", "blog.imyzt.top"};
        String text = FMT."""
                My name is \{name}
                My blog address is \{blogAddress[0].toUpperCase()}, \{blogAddress[1].toLowerCase()}""";
        System.out.println(text);

        System.out.println(STR."\{Math.random()}");
        System.out.println(STR."\{Integer.MAX_VALUE}");
        System.out.println(STR."\{index++}, \{++index}");

        //hello world
        //java.lang.StringTemplate$$Lambda/0x00000001260457f0@33c7353a
        //My name is yzt
        //My blog address is IMYZT.TOP, blog.imyzt.top
        //0.9361799484353136
        //2147483647
        //现在的时间是: 2023-12-20
        //0, 2
    }
}

自定义StringTemplate


import java.util.Iterator;

/**
 * @author imyzt
 * @date 2023/12/20
 * @description 自定义StringTemplate
 */
public class CustomTemplate {

    public static void main(String[] args) {

        var INTER = StringTemplate.Processor.of((StringTemplate st) -> {
            StringBuilder sb = new StringBuilder();
            Iterator<String> fragments = st.fragments().iterator();
            for (Object value : st.values()) {
                sb.append(fragments.next());
                sb.append(value);
            }
            sb.append(fragments.next());
            return sb.toString();
        });

        String name = "yzt";
        int index = 0;
        String text = INTER."""
                {
                "name":\{name},
                "index":\{++index}
                }
                """;
        System.out.println(text);
        //{
        //"name":yzt,
        //"index":1
        //}
    }
}

Record Patterns

记录模式更强大了,支持直接在表达式中创建和使用。还支持嵌套

/**
 * @author imyzt
 * @date 2023/12/19
 * @description 记录模式
 */
public class RecordPatterns {

    public static void main(String[] args) {

        Object p = new Point(1, 2);
        if (p instanceof Point(int x, int y)) {
            System.out.println(x + y);
        }

        Object w = new Window(new Point(1, 2), 3);
        if (w instanceof Window(Point(int x, int y), int z)) {
            System.out.println(x + y + z);
        }

    }
}
record Point(int x, int y) {}
record Window(Point p, int z) {}

ScopedValue

ScopedValue是一个隐藏的方法参数,只有方法可以访问ScopedValue,它可以让两个方法传递参数时无需声明形参(对于Web项目中传递用户信息是非常场景的操作)

ThreadLocal的问题

通常对于上述场景,都会采用ThreadLocal来解决,但是由于ThreadLocal在设计上的瑕疵,导致一直有以下问题:

  1. 内存泄露,在使用完ThreadLocal之后,若没有调用remove方法,会出现内存泄漏。
  2. 增加开销,在具有继承关系的线程中,子线程需要为父线程中ThreadLocal里面的数据分配内存
  3. 不是不可变对象,在方法中可以随意调用set方法篡改。

随着虚拟线程的到来,内存泄漏问题不用担心了,因为虚拟线程会很快的终止,此时会自动删除ThreadLocal中的数据,这样就不用调用remove方法了。
但是虚拟线程的数量是非常多的,假如有上百万个虚拟线程都要拷贝一份ThreadLocal中的变量(问题2),内存将会被严重的浪费掉

示例

示例代码中通过模拟一个“送礼物”的场景,来演示ScopedValue

  1. giveGift 方法中,将礼物送出,是500元
  2. receiveMiddleMan 方法模拟中间人抽成的场景,抽成后送出仅 200元
  3. receiveGift 方法中 ,对 ScopedValue 进行获取,得到的就是200元

但整个过程中,中间人 receiveMiddleMan 在送出前后获取到的信息都是 500元,这正是 Scope 的含义,修改只在本作用域(方法)中生效

/**
 * @author imyzt
 * @date 2023/12/20
 * @description ScopedValue
 */
public class ScopedValueTest {

    private static final ScopedValue<String> GIFT = ScopedValue.newInstance();

    public static void main(String[] args) {

        ScopedValueTest test = new ScopedValueTest();
        test.giveGift();
        //中间人开始: 500
        //收礼物: 200
        //中间人结束: 500
    }

    private void giveGift() {
        ScopedValue.where(GIFT, "500").run(this::receiveMiddleMan);
    }

    private void receiveMiddleMan() {
        System.out.println(STR."中间人开始: \{GIFT.get()}");
        ScopedValue.where(GIFT, "200").run(this::receiveGift);
        System.out.println(STR."中间人结束: \{GIFT.get()}");
    }

    private void receiveGift() {
        System.out.println(STR."收礼物: \{GIFT.get()}");
    }
}

Switch 又升级了,支持when关键字了

switch 可谓是升级升级再升级,我还是在用 if/else 啊。
从12开始,一路升级。

  1. 支持 -> 表达式(Java 12)
  2. 支持返回值(Java 12)
  3. case支持单行写多个条件(Java 12)
  4. 新增yield关键字返回switch的值(Java 12)
  5. 引入模式匹配(Java 15)
  6. 这回又新增了一个when关键字(都快改成SQL了) (Java 21)

Java switch升级之路

/**
 * @author imyzt
 * @date 2023/12/20
 * @description Switch 又升级了~, 这次支持when关键字了
 */
public class SwitchFuture {

    void main() {

        var str = "yes";

        var result = switch (str) {
            case null -> "空对象";
            case String s
                    when "yes".equals(s) -> {
                System.out.println("确定");
                yield "字符串的Yes";
            }
            case String s
                    when "no".equals(s) -> {
                System.out.println("取消");
                yield "字符串的No";
            }
            default -> "default";
        };

        System.out.println(result);
        //确定
        //字符串的Yes
    }
}

简化了main方法(注意看上一个章节的main方法)

  • Class声明和强制的public访问修饰符是必须的。当用在外部组件定义良好的接口封装代码单元时,它们很有用。但在这个小例子中,它们毫无意义。
  • String[]参数主要用于将代码与外部组件(在本例中为操作系统的shell,接收命令传入的参数)连接。它在这里很神秘且无用,尤其是它从未被使用过。
  • static修饰符是Java类和对象模型的一部分。对于新手来说,这不仅是神秘的,而且是有害的:要添加更多可以调用和使用的方法或字段,学​​生必须要么将它们全部声明(传播一种既不常见也不是好习惯的用法),或者就要面对是否有static修饰的区别问题,并学习如何实例化对象。

未命名模式和变量(Go:和我有点像)

/**
 * @author imyzt
 * @date 2023/12/21
 * @description 像golang学习, 支持不使用的变量不命名了
 */
public class UnnamedVariable {

    void main() {

        List<String> list = new ArrayList<>();
        list.add("first");
        list.add("last");

        try {
            System.out.println(STR."first -> \{list.getFirst()}");
            System.out.println(STR."last -> \{list.getLast()}");
            System.out.println("try");
        } catch (Exception _) {
            System.out.println("异常了, 但是我没有 Exception");
        }
    }
}

有序集合

Sequenced Collections

Sequenced Collections引入了三个新接口:

  • SequencedCollection
  • SequencedMap
  • SequencedSet
    接口都附带了一些新方法,可以提高操作集合的效率。比如:java.util.SequencedCollection#reversed等等。

集合补充了获取收尾元素的方法(hutool:当我不存在?)

List<String> list = new ArrayList<>();
list.add("first");
list.add("last");
System.out.println(STR."first -> \{list.getFirst()}");
System.out.println(STR."last -> \{list.getLast()}");

加了一些工具类

  1. StringBuilder中的repeat方法
  2. 补充java.lang.Character#isEmoji
  3. 等等
/**
 * @author imyzt
 * @date 2023/12/21
 * @description 加了些新方法
 */
public class SomethingExample {

    void main() {

        System.out.println(STR."repeat => \{new StringBuffer().repeat("*", 10)}");
        //repeat => **********

        var happy = "你开心吗? 😄";
        System.out.println(STR."isEmoji => \{happy.codePoints().anyMatch(Character::isEmoji)}");
        //isEmoji => true
    }
}