Java 22 更新介绍和示例代码

3月19日 java22正式发布。总计12个新特性发布,4项语言改进、5项库Api改进、1项GC改进和1项java工具改进。这12个更新中,正式发布的有4项内容,其余则为预览或者孵化特性。尝试新特性的朋友可以在下面的链接下载最新版本jdk www.oracle.com/java/techno... 下面为本次更新的简要介绍,后文会有详细的使用示例,代码在 github.com/edfeff/java...

更新介绍

4项语言改进(重点关注,下面有详细示例)

  1. 未命名变量 (正式发布)

可以使用下划线_来作为变量名字,用于必须声明但不使用的场景,比如for循环变量、模式匹配解构参数、try语句块以及异常捕获中。

  1. 子类构造函数中语句可以写在spuer()之前(预览)

子类构造函数的第一行代码可以不用为super()了,java语言之前会限制必须在第一行中调用父类构造函数,jvm规范中并没有此要求,此次放开后构造函数就更灵活了 :( 。

  1. 字符串模板(第二次预览)

此次为第二次预览,正式发布应该是java 24了,预计明年3月份。不过java的字符串模板和其他语言中的字符串模板用法不太一样。java使用字符串处理器的方式实现,不仅仅用于渲染字符串,还能生成任意类型的对象,详细示例见下文。

  1. 隐式类声明和实例Main方法(第二次预览)

作为降低java入门门槛的重要特性,本次更新将支持非public类、非静态方法作为启动类和main方法,可以直接使用 void main(){}来启动java代码了,再配合下文的多文件运行特性,java的使用更便捷了。

5项库API改进

  1. 外部函数API和内存API

与JVM外部的函数和数据进行互操作,比如调用本地库和访问本地内存。提供了比JNI更安全的一套API。详见 openjdk.org/jeps/454

  1. 类文件API

提供一套标准的解析、生成和转换Java类文件的API,并最终替换掉当前的ASM。(ASM: "???")从提案中的动机来看,是嫌弃ASM这些框架跟不上java的升级节奏了。

  1. 流收集API

加入一个非常有用的gather中间操作函数,可以参考此篇文 mp.weixin.qq.com/s/n7nJPFdU3...

  1. 结构化并发

Loom项目中的一个提案,用于简化并发编程的难度,使用过kotlin的协程就非常能理解这一点。传统方式上,当一个大任务切分成多个子任务交给线程池执行时,多个子任务会跑在不同线程上,主线程阻塞等待所有子任务的完成。这些子任务之间的线程是独立的,一个子任务的异常影响不到另一个子任务,外部想取消整个任务时操作必须非常谨慎。而结构化并发则会在大任务切分成子任务时,建立主线程和各个子线程的层次关系,主任务的取消会同时取消掉子任务,子任务的异常会反馈给主任务,在虚拟线程的场景中更加适合。

  1. 作用域值

也是Loom项目中的一个提案,在线程和子线程中共享不可变数据的一种API,更加适合虚拟线程和结构性并发组合使用。在框架或者长链路的调用中,都会选择使用ThreadLocal传递参数。但ThreadLocal存在一些设计缺陷: a. 线程中的任意地方都可以对ThreadLocal进行修改,导致数据变化的不可追溯。b.ThreadLocal的生命周期和线程一样长,内存泄漏风险大。c. ThreadLocal可以被子线程继承,子线程会拷贝一份父线程的变量,占用大量的内存。作用域值是解决海量虚拟线程中数据共享的一个方案。

  1. Vector Api

此Vector是用于利用底层硬件来加速向量计算的,不是很少被人使用的java.util.Vector。 不熟悉向量计算,更多信息可以参考zhuanlan.zhihu.com/p/676227467

1项GC改进

  1. 固定G1的Region

通过在 G1 中实现Region固定来减少GC延迟,这样在JNI的关键区域内不需要禁止GC。JNI中定义了获取对象指针和释放指针的函数,用于和底层语言(C,C++)互操作,在获取对象指针和释放对象指针之间的代码被称为关键区域。JVM在GC时,特别注意在关键区域中不能移动这些被操作的对象。之前的做法是线程处于关键区域时就禁止GC,而现在可以把上面的对象固定在它们的位置上,从而实现GC线程不用在JNI的关键区域暂停,可以减少延迟。

1项java工具改进

  1. 直接运行多文件源码

在jdk17中支持了直接通过java命令运行java源代码,不需要先使用javac编译,不过jdk17中仅仅支持单文件的运行,而此次改进将支持多文件运行,java会自动加载启动类关联的文件。

语言改进示例

未命名变量(JEP456)

当声明一个不需要使用的变量时,Java语言要求必须给变量取一个名字,而在java22中,可使用下划线_来作为这个变量的名字,称之为 未命名变量。(python、golang等微微一笑) 下面举几个例子帮助理解:

例1-计算Iterator的大小

有时我需要获得Iterable的大小,在旧版本中我使用for循环时必须指定变量的名字,而在新版本中可以使用_代替

java 复制代码
/** 未命名变量示例1 */
import java.util.*;

public class Jep456_E1 {
    public static void main(String[] args) {
        oldCode();
        newCode();
    }

    private static void oldCode() {
        Iterable<Integer> list = Arrays.asList(1, 2, 3);
        var total = 0;
        for (var unused : list) {//变量unused从未使用
            total++;
        }
        System.out.printf("old total=%d\n", total);
    }

    private static void newCode() {
        Iterable<Integer> list = Arrays.asList(1, 2, 3);
        var total = 0;
        for (var _ : list) { //使用 _ 占位即可
            total++;
        }
        System.out.printf("new total=%d\n", total);
    }
}

例2-不关心的异常

在某些业务中我们会利用异常来做一些兜底逻辑,异常的原因不是我们关注的重点,此时可使用_占位。 比如下面将字符串转成数字,失败时返回默认值0。

java 复制代码
/**
 * 未命名变量示例2
 */

public class Jep456_E2 {
    public static void main(String[] args) {
        System.out.println(oldGetStringValue("不能转成数字"));//0
        System.out.println(newGetStringValue("不能转成数字"));//0
    }

    private static int oldGetStringValue(String s) {
        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException unused) { // 这里的 unused 未使用
            return 0;
        }
    }

    private static int newGetStringValue(String s) {
        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException _) { // 使用 _ 占位
            return 0;
        }
    }
}

例3-Lambda语法强制要求变量名

在Stream中有些转换操作中不需要流中的变量,但是语法要求必须写变量名,这时可以使用_代替。比如下面将小写字母List转成Map,Map的键是大写字母,值都是固定的布尔true。

java 复制代码
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 未命名变量示例3
 */

public class Jep456_E3 {
    public static void main(String[] args) {
        oldLambda();
        newLambda();
    }

    private static void oldLambda() {
        List<String> list = Arrays.asList("a", "b", "c");
        var upperCharters = list.stream()
                            .collect(Collectors.toMap(String::toUpperCase, v -> true));
        System.out.println(upperCharters);//{A=true, B=true, C=true}
    }

    private static void newLambda() {
        List<String> list = Arrays.asList("a", "b", "c");
        var upperCharters = list.stream()
                            .collect(Collectors.toMap(String::toUpperCase, _ -> true));
        System.out.println(upperCharters);//{A=true, B=true, C=true}
    }
}

例4-Switch表达式忽略具体值

在switch表达式中有时不需要关注具体的变量,而是需要变量的类型时,可以使用_占位。 比如下面代码中,定义了密封类Color和两个子类WhiteBlack。在switch表达式中,变量color具体的值不重要,只需要根据类型来处理不同的逻辑即可,那么可使用_来忽略变量名。

java 复制代码
public class Jep456_E4 {
    static sealed abstract class Color permits White, Black {
    }

    static final class White extends Color {
    }

    static final class Black extends Color {
    }


    public static void main(String[] args) {
        oldSwitchCode();//White
        newSwitchCode();//White
    }

    private static void oldSwitchCode() {
        Color color = new White();
        switch (color) {
            //变量 unusedWhite 和 unusedBlack 都没有使用
            case White unusedWhite -> System.out.println("White");
            case Black unusedBlack -> System.out.println("Black");
        }
    }

    private static void newSwitchCode() {
        Color color = new White();
        switch (color) {
            //用 _ 占位
            case White _ -> System.out.println("White");
            case Black _ -> System.out.println("Black");
        }
    }
}

例5-在模式匹配中忽略不需要的值

java17中引入的模式匹配中,可以通过instanceof对参数进行匹配解构,在java22中继续改进这一特性,支持未命名变量占位。 比如下面的例子,ColoredPointPointColor组成,在模式匹配中我们可能只需要使用到其中的Point参数,或者只需要使用Point的x参数,那么可以用_来忽略其他参数。

java 复制代码
record Point(int x, int y) {
}

enum Color {RED, GREEN, BLUE}

record ColoredPoint(Point p, Color c) {
}

public class Jep456_E5 {
    public static void main(String[] args) {
        oldInstanceOfPattern();
        newInstanceOfPattern();
    }

    private static void oldInstanceOfPattern() {
        var cp = new ColoredPoint(new Point(3, 4), Color.GREEN);
        if (cp instanceof ColoredPoint(Point p, Color c)) {
            //1 只需要Point参数 p.x=3 p.y=4
            System.out.printf("1 只需要Point参数 p.x=%d p.y=%d\n", p.x(), p.y()); 
        }
        if (cp instanceof ColoredPoint(Point p, Color c)) {
            //2 只需要Color参数 color=GREEN
            System.out.printf("2 只需要Color参数 color=%s\n", c); 
        }
        if (cp instanceof ColoredPoint(Point(int x, int y), var c)) {
            //3 只需要坐标参数 x=3 y=4
            System.out.printf("3 只需要坐标参数 x=%d y=%d\n", x, y); 
        }
        if (cp instanceof ColoredPoint(Point(int x, int y), var c)) {
            //4 只需要坐标x参数 x=3
            System.out.printf("4 只需要坐标x参数 x=%d\n", x); 
        }
    }

    private static void newInstanceOfPattern() {
        var cp = new ColoredPoint(new Point(3, 4), Color.GREEN);
        if (cp instanceof ColoredPoint(var p, _)) {
            //1 只需要Point参数 p.x=3 p.y=4
            System.out.printf("1 只需要Point参数 p.x=%d p.y=%d\n", p.x(), p.y()); 
        }
        if (cp instanceof ColoredPoint(_, var c)) {
            //2 只需要Color参数 color=GREEN
            System.out.printf("2 只需要Color参数 color=%s\n", c); 
        }
        if (cp instanceof ColoredPoint(Point(var x, var y), _)) {
             //3 只需要坐标参数 x=3 y=4
            System.out.printf("3 只需要坐标参数 x=%d y=%d\n", x, y);
        }
        if (cp instanceof ColoredPoint(Point(var x, _), _)) {
            //4 只需要坐标x参数 x=3
            System.out.printf("4 只需要坐标x参数 x=%d\n", x); 
        }
    }
}

参考链接

  1. blogs.oracle.com/java/post/t...
  2. openjdk.org/projects/jd...

支持super前语句 (预览 JEP447)

之前的java版本中,Java语法的要求在子类构造函数中,super()语句必须为第一行代码。这个要求是java语法的要求,而不是jvm规范的要求。在此提案中,子类可以将super()语句放在非首行。 示例代码如下

java 复制代码
public class Jep447_E1 {
    public static void main(String[] args) {
        new Son();
    }

    public static class Parent {
        public Parent() {
            System.out.println("parent init");
        }
    }

    public static class Son extends Parent {
        public Son() {
            System.out.println("son init");
            super();
        }
    }
}

执行命令为

bash 复制代码
java.exe --enable-preview --source 22 Jep447_E1.java

输出结果为子类的代码先于父类的构造方法执行,

字符串模板(第二次预览 JEP459)

对于字符串模板,相信使用js、python的朋友很熟悉了,其他jvm语言也很早很早就有这个特性了,java的更新太慢了,模板字符串有那么难吗?为什么还不正式发布! 下面先看下其他语言的字符串模板,然后java中的字符串模板。 javascript的

javascript 复制代码
let first = "hello"
let second = "world"
console.log(`${first} ${second.toLocaleUpperCase()}`) //hello WORLD

python的

python 复制代码
first = "hello"
second = "world"
print(f"{first} {second.upper()}") # hello WORLD

groovy的

groovy 复制代码
def first = "hello"
def second = "world"
println "$first ${second.toUpperCase()}" //hello WORLD

kotlin的

kotlin 复制代码
fun main() {
    val first = "hello"
    val second = "world"
    println("$first ${second.uppercase()}")
}

除此之外还有很多的语言都支持字符串模板,看下官方给出的对比示例 好了既然这个多语言都选择${}或者其他方式的字符串插值来实现,那么Java呢? 当当当当!

javascript 复制代码
public class Jep459_E1 {
    public static void main(String[] args) {
        var first = "hello";
        var second = "world";
        System.out.println(STR."\{first} \{second.toUpperCase()}"); //hello WORLD
    }
}

STR.+\{variable}

不是,能不能学习python、C#啊!看看groovy和kotlin啊!真的是有亿点点丑! STR只是一个静态变量,来自java.lang.StringTemplate.STR,可以用其他字符代替,比如下面的例子

javascript 复制代码
import static java.lang.StringTemplate.STR;

public class Jep459_E2 {
    public static void main(String[] args) {
        var f = STR;
        var first = "hello";
        var second = "world";
        System.out.println(f."\{first} \{second.toUpperCase()}");
    }
}

java的字符串模板不仅仅用于生成字符串,可以生成任意类型对象。这里的原理是java会把字符串模板按照变量占位符拆分,拆成字符串片段数组变量值数组 ,并封装成StringTemplate类型,回调 StringTemplate.Processorprocess方法。 比如下面的例子中,我们的字符串模板是"A \{a} B \{b}",当sampleprocess方法被回调时,参数st的字符串片段fragments[A , B , ],变量值values[1, 2],我们可以使用这两个数组转成任意类型的对象返回。

javascript 复制代码
public class Jep459_E3 {
    record Sample() implements StringTemplate.Processor<Object, Exception> {
        @Override
        public Object process(StringTemplate st) throws Exception {
            System.out.println(st.fragments()); //[A ,  B , ]
            System.out.println(st.values());//[1, 2]
            return st.interpolate();
        }
    }

    public static void main(String[] args) throws Exception {
        var sample = new Sample();
        var a = 1;
        var b = 2;
        Object s = sample."A \{a} B \{b}";
        System.out.println(s); //A 1 B 2
    }
}

官方的QueryBuilder的示例

javascript 复制代码
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Jep459_E4 {
    record QueryBuilder(Connection conn) implements StringTemplate.Processor<PreparedStatement, SQLException> {
        public PreparedStatement process(StringTemplate st) throws SQLException {
            //1. 使用 ? 占位符连接sql片段
            String query = String.join("?", st.fragments());
            //2. 构造PreparedStatement对象,防止sql注入问题
            PreparedStatement ps = conn.prepareStatement(query);
            //3. 按照类型填充参数
            int index = 1;
            for (Object value : st.values()) {
                switch (value) {
                    case Integer i -> ps.setInt(index++, i);
                    case Float f -> ps.setFloat(index++, f);
                    case Double d -> ps.setDouble(index++, d);
                    case Boolean b -> ps.setBoolean(index++, b);
                    default -> ps.setString(index++, String.valueOf(value));
                }
            }
            //4. 返回PreparedStatement对象
            return ps;
        }
    }

    public static void main(String[] args) throws SQLException {
        Connection conn = null;//todo
        var DB = new QueryBuilder(conn);
        String name = "wppcafe";
        PreparedStatement ps = DB."SELECT * FROM Person p WHERE p.last_name = \{name}";
        ResultSet rs = ps.executeQuery();
    }
}

这样看来,Java的字符串模板确实更灵活了一些,但还是丑!

隐式类声明和实例Main方法(第二次预览 JEP463)

这个提案主要是降低java初学者的学习门槛,简化hello world的写法。

  • 隐式类声明: 单个java文件可以不用声明class,直接写字段和方法
  • 实例main方法: main入口方法不再要求必须静态和入参

一般的java的入门程序如下,里面有3点是是初学者难以理解的

  1. 第一点public class HelloWorld是什么意思?
  2. 第二点public static void main(String[] args是什么意思?
  3. 第三点System.out.println("Hello, World!")是什么意思?

因为这个入门程序里面包含了java的很多核心概念,比如类``访问控制``静态方法``入口方法``函数参数``数组``静态引用

javascript 复制代码
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

那么这个提案可以简化到什么程度呢?看例子

  1. 类public 修饰符不需要了
  2. 方法public修饰符不需要了
  3. 方法static声明不需要了
  4. main方法入参不需要了
javascript 复制代码
class Jep463_E1 {
    void main() {
        System.out.println("hello world");
    }
}

还能不能再简化呢?也可以

  1. 类不需要了(直接main方法)
javascript 复制代码
void main() {
    System.out.println("hello world");
}

还可以使用变量和定义方法

javascript 复制代码
String name = "World";

String hello(String name) {
    return "Hello " + name;
}

void main() {
    System.out.println(hello(name));
}

这样看来入门java就简单多了,上面的这个代码就使用了隐式类声明,上面的所有代码被包裹在一个匿名类中,里面声明了实例main方法,再配合java 17中的单文件运行,直接 java hello.java就把程序跑起来了!

库API改进示例

类文件API(预览 JEP457)

写了一个字节码版本的helloworld如下,这部分api了解即可

javascript 复制代码
import java.io.IOException;
import java.lang.classfile.ClassFile;
import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;

void main() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
    final var className = "Hello";
    byte[] bytes = ClassFile.of().build(ClassDesc.of(className), classBuilder -> {
        classBuilder.withMethod("<init>", MethodTypeDesc.ofDescriptor("()V"), ClassFile.ACC_PUBLIC, methodBuilder -> {
            methodBuilder.withCode(codeBuilder -> {
                codeBuilder.aload(codeBuilder.receiverSlot());
                codeBuilder.invokespecial(ClassDesc.ofDescriptor("Ljava/lang/Object;"), "<init>", MethodTypeDesc.ofDescriptor("()V"));
                codeBuilder.getstatic(ClassDesc.ofDescriptor("Ljava/lang/System;"), "out", ClassDesc.ofDescriptor("Ljava/io/PrintStream;"));
                codeBuilder.ldc("hello world");
                codeBuilder.invokevirtual(ClassDesc.ofDescriptor("Ljava/io/PrintStream;"), "println", MethodTypeDesc.ofDescriptor("(Ljava/lang/String;)V"));
                codeBuilder.return_();
            });
        });
    });
    ClassLoader classLoader = new ClassLoader() {
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            if (name.equals(className)) {
                return defineClass(className, bytes, 0, bytes.length);
            }
            return super.findClass(name);
        }
    };
    Class<?> helloClass = classLoader.loadClass(className);
    Object o = helloClass.newInstance();
}

结构化并发(第二次预览 JEP 462)

例1-kotlin的结构化并发

看个kotlin的例子比较容易理解, mainTask 内部拆成两个subTask,一个耗时1s,另一个耗时3s

kotlin 复制代码
import kotlinx.coroutines.*

fun main() = runBlocking {
    val mainTask: Job
    var subTask1: Job? = null
    var subTask2: Job? = null
    //父协程
    mainTask = launch {
        //3个子协程
        subTask1 = launch {
            //1秒
            println("subTask1 start")
            delay(1000L)
            println("subTask1 finish")
        }

        subTask2 = launch {
            //3秒
            println("subTask2 start")
            delay(3000L)
            println("subTask2 finish")
        }
    }
    delay(500L)
    //遍历父协程的子协程集
    mainTask.children.forEachIndexed { index, task ->
        when (index) {
            0 -> println("subTask1 === task is ${subTask1 === task}")
            1 -> println("subTask2 === task is ${subTask2 === task}")
        }
    }
    //等待父协程执行完成
    mainTask.join()
    println("finish")
}

输出结果为

kotlin 复制代码
subTask1 start
subTask2 start
subTask1 === task is true
subTask2 === task is true
subTask1 finish
subTask2 finish
finish

当我需要取消主任务时如下,我只需要调用mainTask.cancel(),两个子任务就会自动取消,这在使用线程池时难度比较大。

kotlin 复制代码
import kotlinx.coroutines.*

fun main() = runBlocking {
    val mainTask: Job
    var subTask1: Job? = null
    var subTask2: Job? = null
    //父协程
    mainTask = launch {
        //3个子协程
        subTask1 = launch {
            //1秒
            println("subTask1 start")
            delay(1000L)
            println("subTask1 finish")
        }

        subTask2 = launch {
            println("subTask2 start")
            delay(3000L)
            println("subTask2 finish")
        }
    }
    delay(500L)
    //取消主任务
    mainTask.cancel()
    println("finish")
}

输出结果为

kotlin 复制代码
subTask1 start
subTask2 start
finish

例2-java的结构化并发

和kotlin的例子一样,mainTask 内部拆成两个subTask,一个耗时1s,另一个耗时3s

kotlin 复制代码
import java.util.concurrent.ExecutionException;
import java.util.concurrent.StructuredTaskScope;

public class Jep462_E3 {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        mainTask();
    }

    public static void mainTask() throws InterruptedException, ExecutionException {
        var scope = new StructuredTaskScope.ShutdownOnFailure();
        var subTask1 = scope.fork(() -> {
            System.out.println("subTask1 start");
            Thread.sleep(1000);
            System.out.println("subTask1 finish");
            return null;
        });
        var subTask2 = scope.fork(() -> {
            System.out.println("subTask2 start");
            Thread.sleep(3000);
            System.out.println("subTask2 finish");
            return null;
        });

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
        }

        scope.join().throwIfFailed();
        scope.close();
        System.out.println("finish");
    }
}

输出为

kotlin 复制代码
subTask1 start
subTask2 start
subTask1 finish
subTask2 finish
finish

当我需要取消主任务时如下,我只需要调用scope.shutdown()

kotlin 复制代码
import java.time.Instant;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.TimeoutException;

public class Jep462_E4 {
    public static void main(String[] args) {
        mainTask();
    }

    public static void mainTask() {
        var scope = new StructuredTaskScope.ShutdownOnFailure();
        var subTask1 = scope.fork(() -> {
            System.out.println("subTask1 start");
            Thread.sleep(1000);
            System.out.println("subTask1 finish");
            return null;
        });
        var subTask2 = scope.fork(() -> {
            System.out.println("subTask2 start");
            Thread.sleep(3000);
            System.out.println("subTask2 finish");
            return null;
        });

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
        }
        scope.shutdown();
        System.out.println("finish");
    }
}

结果如下

kotlin 复制代码
subTask1 start
subTask2 start
finish

作用域值(第二次预览 JEP464)

在框架中我需要跨多层传递一个参数,但是又不想被用户的代码访问到,就可以使用ScopedValue。比如下面的示例,我需要把用户的处理器MyHandler加入到框架中,又不想MyHandler访问到框架内传输的上下文数据。

kotlin 复制代码
public class Jep464_E1 {
    public static void main(String[] args) {
        var application = new MyHandler();
        var framework = new Framework(application);
        framework.service(new Request(), new Response());
    }

    static class FrameworkContext {
        private long startTime;
        private long spentTime;
    }

    interface Handler {
        void handle(Request request, Response response);
    }

    static class MyHandler implements Handler {
        @Override
        public void handle(Request request, Response response) {
            //这里是无法读取CONTEXT的,限制了用户代码的访问
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    record Request() {
    }

    record Response() {
    }

    static class Framework {
        private final Handler handler;
        private final static ScopedValue<FrameworkContext> CONTEXT
                = ScopedValue.newInstance();   // (1)

        Framework(Handler handler) {
            this.handler = handler;
        }

        void service(Request request, Response response) {
            var context = createContext(request);
            ScopedValue.where(CONTEXT, context)            // (2)
                    .run(() -> { //CONTEXT 在此处后可以访问
                        preRequest();
                        handler.handle(request, response);
                        postRequest();
                        //CONTEXT 在此后不可以访问,限定了context的访问访问
                    });
        }

        private void preRequest() {
            var context = CONTEXT.get();
            context.startTime = System.currentTimeMillis();
        }

        private void postRequest() {
            var context = CONTEXT.get();
            context.spentTime = System.currentTimeMillis() - context.startTime;
            System.out.println("request spent " + context.spentTime + " ms");
        }

        FrameworkContext createContext(Request request) {
            return new FrameworkContext();
        }
    }
}

Java工具改进示例

直接运行多文件源码(Jep 458)

使用两个java文件的示例,一个为启动类,一个为工具类 启动类 Jep458_E1.java

kotlin 复制代码
/**
 * 未命名变量示例3
 */

public class Jep458_E1 {
    public static void main(String[] args) {
        Util.hello();
    }
}

工具类 Util.java

kotlin 复制代码
public class Util {
    public static void hello() {
        System.out.println("hello");
    }
}

使用java命令启动

kotlin 复制代码
java.exe Jep458_E1.java                              

运行结果 (忽略中间的Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8)

相关推荐
勤奋的知更鸟2 分钟前
Java编程之组合模式
java·开发语言·设计模式·组合模式
千|寻2 分钟前
【画江湖】langchain4j - Java1.8下spring boot集成ollama调用本地大模型之问道系列(第一问)
java·spring boot·后端·langchain
程序员岳焱16 分钟前
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
后端·sql·mysql
爱编程的喵16 分钟前
深入理解JavaScript原型机制:从Java到JS的面向对象编程之路
java·前端·javascript
龚思凯22 分钟前
Node.js 模块导入语法变革全解析
后端·node.js
天行健的回响24 分钟前
枚举在实际开发中的使用小Tips
后端
on the way 12327 分钟前
行为型设计模式之Mediator(中介者)
java·设计模式·中介者模式
保持学习ing29 分钟前
Spring注解开发
java·深度学习·spring·框架
wuhunyu30 分钟前
基于 langchain4j 的简易 RAG
后端
techzhi30 分钟前
SeaweedFS S3 Spring Boot Starter
java·spring boot·后端