你好,我是看山。
本文收录在 《从小工到专家的 Java 进阶之旅》 系列专栏中。
从 2017 年开始,Java 版本更新策略从原来的每两年一个新版本,改为每六个月一个新版本,以快速验证新特性,推动 Java 的发展。从 《JVM Ecosystem Report 2021》 中可以看出,目前开发环境中有近半的环境使用 Java8,有近半的人转移到了 Java11,随着 Java17 的发布,相信比例会有所变化。
因此,准备出一个系列,配合示例讲解,阐述各个版本的新特性。
Java17 是在 2021 年 9 月发布的一个 LTS 版本(长期支持版本),上一个长期支持版是 Java11,于 2018 年 9 月发布。从目前来看,Java11 在市场占有率已进过半,如果错过了升级 Java11,我们可不要错过这次的升级。接下来我们看看 Java17 为我们带来了哪些新增特性:
接下来我们一起看看这些特性。
这个特性是利好科学计算中的浮点运算,保证浮点运算中的 strict 或 strictfp 在每个平台上都能够得到相同的结果,也就是可以把 strictfp 扔了。
在 Java1.2 之前,精确浮点计算是通过迂回的方式实现的。大约从 2001 年开始,奔腾 4 和更高版本的处理器中提供了 SSE2 扩展(数据流单指令多数据扩展指令集 2),可以直接支持严格的 JVM 浮点运算,不需要额外的开销。那个时候 Intel 和 AMD 还不支持这种扩展,于是 Java1.2 的浮点运算就分叉了。
到后来 Intel 和 AMD 也开始支持 SSE2 和更高版本的扩展指令集,Java 语言就可以恢复到严格的浮点运算了。连 Java 之父 James Gosling 在 Twitter 也发文庆祝:
这个特性是为伪随机数生成器 RPNG(Pseudo-Random Number Generators)增加了新的接口类型和实现,可以更容易地互换使用不同的算法,而且它还为基于流的编程方式提供了更好的支持。这个特性的目标有四个:
java.util.Random
类的现有行为,做好向下兼容。新增了java.util.random.RandomGenerator
接口,作为所有 PRNG 算法的统一 API,提供了工厂类java.util.random.RandomGeneratorFactory
,借助java.util.ServiceLoader.load()
的能力加载各种 PRNG 算法实现,可以构造RandomGenerator
实例。
我们遍历一下看看有哪些 PRNG 算法:
RandomGeneratorFactory.all().forEach(factory -> {
System.out.println(factory.group() + ":" + factory.name());
});
结果是:
LXM:L32X64MixRandom
LXM:L128X128MixRandom
LXM:L64X128MixRandom
Legacy:SecureRandom
LXM:L128X1024MixRandom
LXM:L64X128StarStarRandom
Xoshiro:Xoshiro256PlusPlus
LXM:L64X256MixRandom
Legacy:Random
Xoroshiro:Xoroshiro128PlusPlus
LXM:L128X256MixRandom
Legacy:SplittableRandom
LXM:L64X1024MixRandom
Legacy:Random
就是我们常用的java.util.Random
,我们来试试看:
RandomGenerator randomGenerator = RandomGeneratorFactory.of("Random")
.create(System.currentTimeMillis());
System.out.println(randomGenerator.getClass());
System.out.println(randomGenerator.nextInt(10));
结果是:
class java.util.Random
6 (这个值随不同的运行结果不同)
我们还可以使用流式编程方式批量获取随机数:
final IntStream ints = RandomGeneratorFactory.of("L128X128MixRandom")
.create()
.ints(10, 0, 100);
System.out.println(Arrays.toString(ints.toArray()));
结果会得到 10 个随机数字数组(每次运行结果不同):
[50, 16, 73, 4, 79, 32, 55, 34, 40, 53]
MacOS 为了提升图形渲染性能,在 2018 年 9 月放弃之前的 OpenGL 渲染库,选用了 Apple Metal。从 Java17 开始,Swing API 内部用于渲染 Java 2D 的 API 开始使用新的 Apple Metal 加速渲染 API。
默认情况下,这个功能不启用,需要主动开启:
-Dsun.java2d.metal=true
这个特性改动是属于 API 内部实现,使用上没有任何差别。而且对 MacOS 的系统版本有要求,需要在 MacOS10.14 版本或以上,否则还是会使用 OpenGL 渲染图形。
苹果在 2020 年 6 月的 WWDC 的演讲中宣布,将开启长期将 Macintosh 系列从 x64 过渡到 AArch64 的计划,该特性主要是为了适应这种改变。
Linux 的 AArch64 支持是在 Java9 提供的(参见 Java9 的新特性),Windows 的 AArch64 支持是在 Java16 提供的(参见 Java16 的新特性)。
在 Java12 的时候对 AArch64 的支持库进行了统一,只保留了一套维护代码(参见 Java12 的新特性)。
在 Java16 中为了改进 JDK 的安全性和可维护性,对内部 API 进行了封装,但是也留了后门,可以使用启动参数--illegal-access
控制内部 API 的封装程度。(参见 Java16 的新特性)
到了 Java17 中,除了sun.misc.Unsafe
可以使用,其他的内部 API 都变成了强封装模式,而且--illegal-access
命令也被移除,如果还在命令中添加该参数,会直接报错:
~ $ java -version
openjdk version "17.0.1" 2021-10-19
OpenJDK Runtime Environment (build 17.0.1+12-39)
OpenJDK 64-Bit Server VM (build 17.0.1+12-39, mixed mode, sharing)
~ $ java --illegal-access
Unrecognized option: --illegal-access
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
密封类特性是在 Java15 提供预览版,Java16 提供第二版预览,终于在 Java17 中成为正式功能。该特性限制哪些其他类或接口可以扩展或实现密封组件。
JEP 409 并没有对密封类有新的特性,可以参考 Java15 的新特性、Java16 的新特性,这里不再重复。
Java 对象序列化是一个非常重要的功能,可以透明化远程处理,也促进了 JavaEE 的成功。序列化过程没有问题,但是反序列化过程可能存在危险:
终于在 Java17 中增加了反序列化过滤器,允许应用程序使用 JVM 范围的过滤器工厂,配置特定于上下文和动态选择的反序列化过滤器,该工厂用于为每个反序列化操作选择一个过滤器。
简单点说,就是提前说好可以反序列化那些类,如果序列化数据流中包含不被允许的类对象,就直接报错。
这个特性功能很赞,在 Java14 中正式提供 Switch 表达式特性(参见 Java14 的新特性),本次提供的是 Switch 模式匹配与 instanceof 模式匹配有些类似,是能够在 Switch 表达式实现类型自动转换。
比如:
static String formatterPatternSwitch(Object o) {
return switch (o) {
case null -> "null";
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.getClass().getSimpleName() + " " + o;
};
}
public static void main(String[] args) {
System.out.println(formatterPatternSwitch(null));
System.out.println(formatterPatternSwitch("1"));
System.out.println(formatterPatternSwitch(2));
System.out.println(formatterPatternSwitch(3L));
System.out.println(formatterPatternSwitch(4.0));
System.out.println(formatterPatternSwitch(new AtomicLong(5)));
}
结果是:
null
String 1
int 2
long 3
double 4.000000
AtomicLong 5
可以看到,不只是类型自动转换,还可以直接判断是否是null
,省了前置判断对象是否是null
了。
期待这个功能早日转正。
Java 程序可以通过该 API 与 Java 运行时之外的代码和数据互操作。通过有效地调用外部函数(即 JVM 外部的代码),并通过安全地访问外部内存(即不由 JVM 管理的内存),该 API 使 Java 程序能够调用本机库并处理本机数据,而不会有 JNI 的脆弱性和危险。
通过更加优雅的方式访问外部函数是从 Java14 开始的,经历了多个孵化版本:
可以看出来,虽然一直在孵化,但是功能越来越强大,这一旦孵出来,岂不是超级神兽了。
这一系列的功能都是为了能够在 Java 类中调用 C 语言类库:
private static final SymbolLookup libLookup;
static {
// loads a particular C library
var path = JEP412.class.getResource("/print_name.so").getPath();
System.load(path);
libLookup = SymbolLookup.loaderLookup();
}
第一步,需要加载我们希望通过 API 调用的目标库。
第二步,我们需要指定目标方法的签名,并最终调用它:
public String getPrintNameFormat(String name) {
var printMethod = libLookup.lookup("printName");
if (printMethod.isPresent()) {
var methodReference = CLinker.getInstance()
.downcallHandle(
printMethod.get(),
MethodType.methodType(MemoryAddress.class, MemoryAddress.class),
FunctionDescriptor.of(CLinker.C_POINTER, CLinker.C_POINTER)
);
try {
var nativeString = CLinker.toCString(name, newImplicitScope());
var invokeReturn = methodReference.invoke(nativeString.address());
var memoryAddress = (MemoryAddress) invokeReturn;
return CLinker.toJavaString(memoryAddress);
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}
throw new RuntimeException("printName function not found.");
}
上面这段代码摘自https://www.baeldung.com/java-17-new-features。
Vector 向量计算 API 是为了处理 SIMD(Single Instruction Multiple Data,单指令多数据)类型的操作,即并行执行的各种指令集。它利用支持向量指令的专用 CPU 硬件,并允许以管道的形式执行此类指令。这种运算方式可以让开发人员实现更高效的代码,充分利用底层硬件的潜力。日常使用包括科学代数线性应用程序、图像处理、字符处理、繁重的算术应用程序,以及任何需要对多个独立操作数应用一个运算的应用程序。
Vector 向量计算 API 是在 Java16 引入(参见 Java16 的新特性),可以在运行时借助 CPU 向量运算指令,实现更优的计算能力。在 Java17 中,针对性能和实现进行了改进,包括字节向量与布尔数组之间进行转换。
原来的向量运算我们需要这样写:
for (var i = 0; i < a.length; i++) {
c[i] = a[i] * b[i];
}
现在我们可以这样写:
final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
for (var i = 0; i < a.length; i += SPECIES.length()) {
var m = SPECIES.indexInRange(i, a.length);
var va = FloatVector.fromArray(SPECIES, a, i, m);
var vb = FloatVector.fromArray(SPECIES, b, i, m);
var vc = va.mul(vb);
vc.intoArray(c, i, m);
}
Applet 是用 Java 编写可以嵌入到网页中的小应用,属于已经过时的技术,很多浏览器已经取消支持。Applet API 在 Java9 的时候标记了过期,在 Java17 标记为删除(@Deprecated(since = "9", forRemoval = true)
)。
记得我上学的时候,课本上还有这部分内容。
RMI 激活机制在 Java15 标记了过期(参见 Java15 的新特性),到 Java17 正式删除。这里只是删除了 RMI 激活机制,对于其他 RMI 功能不受影响。
在 Java9 的 JEP 295 中,引入了实验性的提前编译 jaotc 工具,但是这个特性自从引入依赖用处都不太大,而且需要大量的维护工作,所以在 Java17 中决定删除这个特性。
但是保留了实验性的 Java 级 JVM 编译器接口(JVMCI),这样开发人员也可以继续使用外部构建的编译器版本,并使用 Graal 编译器(GraalVM)进行 JIT 编译。
Security Manager 在 JDK1.0 时就已经引入,但是它一直都不是保护服务端以及客户端 Java 代码的主要手段,对于如此鸡肋的功能,最终决定标记为删除(@Deprecated(since="17", forRemoval=true)
)。
本文介绍了 Java17 新增的特性,完整的特性清单可以从 https://openjdk.java.net/projects/jdk/17/ 查看。后续内容会发布在 从小工到专家的 Java 进阶之旅 系列专栏中。
Java17 是 LTS(长期支持版),上个 LTS 版本是 Java11,很多团队已经在生产上切换,相信接下来会有一些团队在测试环境尝鲜。
有人认为 Java8 是神,有人则喜欢不断地尝鲜,你是哪种呢?欢迎在留言说下你在用哪个版本?
青山不改,绿水长流,我们下次见。
你好,我是看山。游于码界,戏享人生。如果文章对您有帮助,请点赞、收藏、关注。我还整理了一些精品学习资料,关注公众号「看山的小屋」,回复“资料”即可获得。
个人主页:https://www.howardliu.cn
个人博文:Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java17 的新特性
CSDN 主页:https://kanshan.blog.csdn.net/
CSDN 博文:Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java17 的新特性