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)

相关推荐
music0ant3 分钟前
Idea 配置环境 更改Maven设置
java·maven·intellij-idea
任小永的博客8 分钟前
VUE3+django接口自动化部署平台部署说明文档(使用说明,需要私信)
后端·python·django
凡人的AI工具箱10 分钟前
每天40分玩转Django:Django类视图
数据库·人工智能·后端·python·django·sqlite
记得开心一点嘛18 分钟前
Nginx与Tomcat之间的关系
java·nginx·tomcat
凡人的AI工具箱20 分钟前
每天40分玩转Django:实操图片分享社区
数据库·人工智能·后端·python·django
界面开发小八哥31 分钟前
「Java EE开发指南」如何用MyEclipse构建一个Web项目?(一)
java·前端·ide·java-ee·myeclipse
王伯爵33 分钟前
<packaging>jar</packaging>和<packaging>pom</packaging>的区别
java·pycharm·jar
Q_19284999061 小时前
基于Spring Boot的个人健康管理系统
java·spring boot·后端
liutaiyi81 小时前
Redis可视化工具 RDM mac安装使用
redis·后端·macos
Q_19284999061 小时前
基于Springcloud的智能社区服务系统
后端·spring·spring cloud