一帆磨砺

生活所迫,一叶孤舟

0%

Java版本特性-JDK18

引用参考文档链接

  1. jdk18特性列表
  2. 迁移到JDK18为何写一个空的finalize()方法?
  3. Java-SE 安全编码指南
  4. Finalization对GC的影响

写在开头

  1. 反射核心重新实现,从API的维护成本和性能都有所提升
  2. 互联网地址解析器可自定义
  3. switch针对case覆盖范围和sealed类的检测进行了加强
  4. Finalization标注弃用,因目前仍默认启用,因此迁移成本暂时无变化,后期该特性正式删除后,迁移的成本估计需要仔细评估。

默认字符集为UTF-8

Java API 默认字符集为UTF-8,除了console I/O

极简web服务器

从官方文档描述,功能基本与nginx重合。并且从Motivation中得知它的定位主要是校园内的教学场景。

案例

代码demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 /**
* snippet
*/
public class CodeSnippets {
/**
* {@snippet :
* CodeSnippets.func(); // @highlight substring="func"
*}
*/
public static void func() {
System.out.println("CodeSnippets");
}

/**
* {@snippet file="CodeSnippets.java" region="test"}
* @param text 打印内容
*/
public static void func2(String text) {
System.out.println("CodeSnippets:func2:" + text);
}

/**
* for region test
*/
public void func3() {
// @start region="test"
CodeSnippets.func2("hihihi"); // @replace regex='".*"' replacement="..."
// @end
}
}

部分注释说明

  • @highlight substring=”func”
    是将当前行的注释java代码中的func高亮
  • // @replace regex=’”.*”‘ replacement=”…”
    当前行在作为注释代码时,通过正则匹配,将“hihihi”替换为…

javadoc脚本

1
javadoc -private -d .../Downloads/test --source-path ".../JavaFeature/JDK18/src/main/java:.../JavaFeature/JDK18/src/main/resources" ".../JavaFeature/JDK18/src/main/java/module-info.java" ".../JavaFeature/JDK18/src/main/java/com/janwarlen/feature/CodeSnippets.java" ".../JavaFeature/JDK18/src/main/java/com/janwarlen/Demo.java" --snippet-path ".../JavaFeature/JDK18/src/main/java/com/janwarlen/feature/"

参数说明

  • –source-path 指明有哪些路径需要参与本次生成
  • –snippet-path 标注在使用@snippet 的file文件范围
  • -d doc生成文件存放目录,文件较多,建议单独创建空目录
  • -private 扫描所有文件

结果简单展示

使用Method Handles重新实现反射核心内容

Method Handles 作为反射的底层机制,重新实现了 java.lang.reflect.Method、Constructor、Field 组件,将大大降低反射 API 的维护和开发成本。

This benefits Project Loom by reducing the use of native stack frames.

Vector API (第三次孵化)

变动内容:

  • 支持ARM 标量矢量扩展 (SVE) 平台。
  • 在支持硬件掩码的架构上提高接受掩码的向量操作的性能。

互联网地址解析 SPI

为主机名和地址解析定义服务提供者接口 (SPI),以便java.net.InetAddress可以使用平台内置解析器以外的解析器。
在尝试自定义解析器的过程中发现一个难以解决的问题,InetAddress是通过ServiceLoader.load(InetAddressResolverProvider.class)去找所有实现的解析器,但是用户自定义的解析器所在的module不在java.baserequires/uses中,根据模块化的隔离,此时java.base是看不到自定义实现的解析器的。。。
原因在于如果类在声明了module的工程中,就跳过不load了。。。

1
2
3
4
5
java.util.ServiceLoader.LazyClassPathLookupIterator#hasNextService
if (clazz.getModule().isNamed()) {
// ignore class if in named module
continue;
}

当我删除module-info.java后,自定义的解析器就可以被识别了。

案例

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.net.spi.InetAddressResolver;
import java.util.Arrays;
import java.util.stream.Stream;

public class AddressResolver implements InetAddressResolver {
@Override
public Stream<InetAddress> lookupByName(String host, LookupPolicy lookupPolicy) throws UnknownHostException {
// 自定义根据hostname生成InetAddress流
// 可查找DNS 或者本地的 /etc/hosts
System.out.println("AddressResolver:lookupByName");
InetAddress localHost = Inet4Address.getLoopbackAddress();
return Arrays.stream(new InetAddress[]{localHost});
}

@Override
public String lookupByAddress(byte[] addr) throws UnknownHostException {
// 根据ip找hostname
System.out.println("AddressResolver:lookupByAddress");
return "null";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.net.spi.InetAddressResolver;
import java.net.spi.InetAddressResolverProvider;

public class ResolverProvider extends InetAddressResolverProvider {

static {
System.out.println("classLoader load class:ResolverProvider");
}

@Override
public InetAddressResolver get(Configuration configuration) {
return new AddressResolver();
}

@Override
public String name() {
return "自定义";
}
}

简单调用代码

1
2
3
4
5
6
7
public static void func() {
try {
InetAddress ids = InetAddress.getByName("null");
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}

输出结果

1
2
3
classLoader load class:ResolverProvider
AddressResolver:lookupByName
WARNING: Unknown module: JDK18 specified to --add-exports

调用本地方法和操作堆外内存(第二次孵化)

本次变更内容:

  • 内存访问var句柄中支持更多的载体,如booleanand MemoryAddress;
  • 一个更通用的解引用 API,在MemorySegment和MemoryAddress接口中都可用;
  • 一个更简单的 API 来获取下调用方法句柄,MethodType不再需要传递参数;
  • 一个更简单的 API 来管理资源范围之间的时间依赖关系;和
  • 用于将 Java 数组复制到内存段和从内存段复制的新 API。

挖个坑,后续补上代码demo;

switch匹配模式增强(第二次预调整)

变更内容:

  • 同类型的case必须所有case都有类型声明(我的理解应该产生了偏差),在该案例中,CharSequence的覆盖范围比String大,因此此处就是有问题的。该变动应该是增强了case之间的条件覆盖范围检查。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    static void error(Object o) {
    switch(o) {
    case CharSequence cs ->
    System.out.println("A sequence of length " + cs.length());
    case String s -> // Error - pattern is dominated by previous pattern
    System.out.println("A string: " + s);
    default -> {
    break;
    }
    }
    }
  • sealed进行了拓展支持,当case中出现sealed相关的类时,会检测其他类是否都已覆盖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    sealed interface S permits A, B, C {}
    final class A implements S {}
    final class B implements S {}
    record C(int i) implements S {} // Implicitly final

    static int testSealedExhaustive(S s) {
    return switch (s) {
    case A a -> 1;
    case B b -> 2;
    case C c -> 3;
    };
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    sealed interface I<T> permits A, B {}
    final class A<X> implements I<String> {}
    final class B<Y> implements I<Y> {}

    static int testGenericSealedExhaustive(I<Integer> i) {
    return switch (i) {
    // Exhaustive as no A case possible!
    case B<Integer> bi -> 42;
    }
    }

弃用 Finalization

可以先查看迁移到JDK18为何写一个空的finalize()方法?
官方文档共4个角度决定放弃

  • 安全漏洞(资源泄漏可通过try-with-resourcesCleaners方式避免)
  • 性能
  • 不可靠的执行
  • 困难的编程模型(编码成本较高)

目前仍默认启用,可通过--finalization=disabled禁用

欢迎关注我的其它发布渠道