openJDK官方介绍文档
https://openjdk.java.net/projects/jdk/17/
oracle官方文档
https://docs.oracle.com/en/java/javase/17/index.html
注意:IDEA需要升级到2021版本才能兼容jdk17
下载安装JDK17
官网下载页面
https://www.oracle.com/java/technologies/downloads/
直接下载链接(macOS x64)
https://download.oracle.com/java/17/latest/jdk-17_macos-x64_bin.dmg
PS:可通过在终端中 执行 uname -a 查看系统内核
在新版idea中配置JDK17
language level 切换到 17(preview)
JAVA特性演进
传送门
Java 6 Features: https://openjdk.java.net/projects/jdk6/
Java 7 Features: https://openjdk.java.net/projects/jdk7/
Java 8 Features: https://openjdk.java.net/projects/jdk8/
Java 9 Features: https://openjdk.java.net/projects/jdk9/
Java 10 Features: https://openjdk.java.net/projects/jdk/10/
Java 11 Features: https://openjdk.java.net/projects/jdk/11/
Java 12 Features: https://openjdk.java.net/projects/jdk/12/
Java 13 Features: https://openjdk.java.net/projects/jdk/13/
Java 14 Features: https://openjdk.java.net/projects/jdk/14/
Java 15 Features: https://openjdk.java.net/projects/jdk/15/
Java 16 Features: https://openjdk.java.net/projects/jdk/16/
Java 17 Features: https://openjdk.java.net/projects/jdk/17/
JAVA特性解析
JEP 395:Records
Java 是基于对象的语言,也就是说,Java 是一种 基于指针的间接引用 的语言。这个基于指针的特性,给每个对象带来了唯一标识性。例如判断两个 Object 的 ==,其实判断的是两个对象的内存相对映射地址是否相同,尽管两个对象的 field 完全一样,他们的内存地址也不同。同时这个特性也给对象带来了 多态性 , 易变性 还有 锁 的特性。
但是,并不是所有对象都需要这种特性。由于 指针与间接访问 带来了性能瓶颈,Java 准备对于不需要以上提到的特性的对象移除这些特性。于是,Record 出现了。
相关资料:
字节码分析
使用IDEA 的 jclasslib插件 可以查看record类编译后的字节码
自动生成
private final field
全属性构造器
属性的get方法
hashCode()
toString()
equals()
方法的核心 是 invokedynamic
invokedynamic
Java 最早是一种静态类型语言,即类型检查的主体过程主要是在编译期而不是运行期。
为了兼容动态类型语法,在 Java 7 引入了字节码指令 invokedynamic 。
是后来 Java 8 的拉姆达表达式以及 var 语法的实现基础。
invokedynamic底层依赖一种动态确定目标方法的机制 MethodHandle
通过MethodHandle可以动态的获取方法进行调用,和 Java Reflection 反射类似,但是为了追求性能效率,需要用 MethodHandle。
Reflection 仅仅是 Java 语言上补充针对反射的实现,并没有考虑效率的问题, 尤其是 JIT 基本无法针对这种反射调用进行有效的优化 。
MethodHandle 更是像是对于字节码的方法指令调用的模拟,适当使用的话 JIT 也能对于它进行优化。
Record基本用法
JEP 409:Sealed Classes 密封类
https://openjdk.java.net/jeps/409
Sealed Classes 在Java 17中已经稳定,主要作用就是 通过sealed关键字去限制接口的实现范围。
可以让指定的class实现sealed interface,不允许外部实现sealed interface
这样的好处是实现类是可数的,在代码中就可以进行穷举,可以很好的搭配pattern matching使用。
设计思想
超类应该可以被广泛访问 (因为它代表用户的重要抽象)但不能广泛 扩展(因为它的子类应该仅限于作者已知的那些)。同时,超类不应过度约束其子类,强迫它们成为final或阻止它们定义自己的状态。
sealed class的一些特点
允许扩展的类必须具有规范的名称,否则编译时会报错。这意味着匿名类和局部类不能成为被指定扩展的类
sealed class 和它允许扩展的类必须在同一个模块中
每个被允许扩展的类必须直接继承sealed class
每个被允许扩展的子类必须使用修饰符 final、sealed、non-sealed中的一个
声明为final,则表示该子类不可进一步扩展
声明为sealed,可以进一步限制哪些它的子类进行扩展
声明为non-sealed,可以允许未知的子类对其扩展
package com.example.geometry;
public abstract sealed class Shape
permits Circle, Rectangle, Square, WeirdShape { ... }
public final class Circle extends Shape { ... }
public sealed class Rectangle extends Shape
permits TransparentRectangle, FilledRectangle { ... }
public final class TransparentRectangle extends Rectangle { ... }
public final class FilledRectangle extends Rectangle { ... }
public final class Square extends Shape { ... }
public non-sealed class WeirdShape extends Shape { ... }
如代码中,WeirdShape类可以允许未知的子类进行扩展,但是它的子类的实例仍然也是WeirdShape,所以 Shape类的子类仍旧是可穷举的。
sealed classes 与类型转换
interface I {}
class C {} // does not implement I
void test (C c) {
if (c instanceof I)
System.out.println("It's an I");
}
Java对此类表达式是极为宽容的,因为即使 C 类不实现 I接口 ,它的子类也可能去实现 I 的。
这样的规则体现了开放可扩展性的概念,Java的类型体系,并不是一个封闭的世界,类或者接口,可以在未来的某个时间点被继承或者实现。
下面看这样一个例子
interface I {}
sealed class C permits D {}
final class D extends C {}
void test (C c) {
if (c instanceof I) // Compile-time error!
System.out.println("It's an I");
}
在代码中 c instanceof I 的表达式,会报编译时错误。
因为 C 类指定了允许扩展的子类是D,子类是可穷举的,而D是final的,它没有实现I,同时也不允许其他子类继承它,因此编译器在编译时可以发现这种编译错误。
sealed classes 可以缩小类型转换的范围,使编译器在编译时确定哪些转换是错误的。
sealed classes 与 record classes
record 能够与 sealed 紧密配合,因为record类本身就是隐式声明的final类
package com.example.expression;
public sealed interface Expr
permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { ... }
public record ConstantExpr(int i) implements Expr { ... }
public record PlusExpr(Expr a, Expr b) implements Expr { ... }
public record TimesExpr(Expr a, Expr b) implements Expr { ... }
public record NegExpr(Expr e) implements Expr { ... }
这样组合使用的sealed 和 record 有时被称为 algebraic data types
简而言之,ADT 是通过 代数运算 构造的,常见的两种构造方式为:
sum,即 a or b
product,即 a and b
其中,Record类允许我们表达 product类型,Sealed类允许我们表达sum类型
通过 Record 与 Sealed 组成的 ADT,帮助编译器实现穷尽分析。
sealed Classes 与模式匹配
而ADT的精髓之处,需要通过与模式匹配配合才能表现出来,本次的特性JEP 406将体现Sealed类的显著优势
若不使用模式匹配的代码,可能会这样实现
Shape rotate(Shape shape, double angle) {
if (shape instanceof Circle) return shape;
else if (shape instanceof Rectangle) return shape;
else if (shape instanceof Square) return shape;
else throw new IncompatibleClassChangeError();
}
编译器无法保证if else 涵盖了所有的 instanceof 情况,其实最后一个else 是不可达的,同时如果不验证 shape instanceof Rectangle ,也不会报出编译时错误,但是这样就可能会遗漏一种情况。
若采用了模式匹配,编译器就可以确认覆盖了每一种情况,这样也可以不需要default分支,同时,如果少判断了任何一种情况,编译器会报编译时错误
Shape rotate(Shape shape, double angle) {
return switch (shape) { // pattern matching switch
case Circle c -> c;
case Rectangle r -> shape.rotate(angle);
case Square s -> shape.rotate(angle);
// no default needed!
}
}
JEP 406:模式匹配
void test(Object o) {
// Old code
if (o instanceof String) {
String s = (String)o; //Variable 's' can be replaced with pattern variable
//... use s ...
System.out.println(s.toLowerCase());
}
// New code
if (o instanceof String s) {
//... use s ...
System.out.println(s.toLowerCase());
}
}
模式匹配和Record类
官方文档有一处写错了
https://openjdk.java.net/jeps/405
record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
static void printUpperLeftColoredPoint(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
System.out.println(ul);
}
}
// 此时如果是想要操作 ul中的元素 必须进行判空处理
static void printColorOfUpperLeftPoint(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
if (ul == null) {
return;
}
Color c = ul.c();
System.out.println(c);
}
}
// 而新 嵌套record模式可以解决这个问题
static void printColorOfUpperLeftPoint(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr)) {
System.out.println(c);
}
}
模式匹配和Array数组
static void printFirstTwoStrings(Object o) {
if (o instanceof String[] sa && sa.length >= 2) {
String s1 = sa[0];
String s2 = sa[1];
System.out.println(s1 + s2);
}
}
static void printFirstTwoStrings(Object o) {
if (o instanceof String[] { String s1, String s2, ... }){
System.out.println(s1 + s2);
}
}
模式匹配和Switch
自Java14之前,switch只支持 数字类型、枚举类型、String类型,并且只能验证是否完全相等。
而我们可能会使用模式去匹配同一个变量,最终可能得到一堆if else
static String formatter(Object o) {
String formatted = "unknown";
if (o instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
formatted = String.format("double %f", d);
} else if (o instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
上面的代码,还是得益于使用了模式表达式,但是它仍然有些问题。首先,这样编写是允许潜在的编码错误存在的。其次这样的代码编译器是不能优化它的,经过编译后,它始终是O(n)复杂度,即使潜在问题是O(1)。
而Switch是完美契合模式匹配的,通过swtich重写上面的代码
static String formatterPatternSwitch(Object o) {
return switch (o) {
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.toString();
};
}
这样写,语义更清晰,同时,它是可优化的,这种场景下,我们可以在O(1)时间内完成执行。
之前我们使用switch的时候,如果switch入参为null可能会抛出空指针,所以我们必须要在使用switch之前进行判null操作。
但是现在,switch 允许入参是任何类型的,同时case标签也可以具有模式,因此 null 也可以集成到case中,同时允许为模式匹配添加约束条件。
Shape rotateWithSwitch(Shape shape, double angle) {
switch (shape) {
case Circle c && angle > 20 -> {
c.rotate(angle);
c.rotate(angle);
}
case Rectangle r -> r.rotate(angle);
case Square s -> s.rotate(angle);
case null -> System.out.println("Oops"),
case default -> throw new IncompatibleClassChangeError();
}
return shape;
}
JEP 391:macOS/AArch64 端口
做的是什么?
将 JDK 移植到 macOS/AArch64。
为什么这么做?
Apple 宣布了一项将其 Macintosh 计算机系列从 x64 过渡到 AArch64的长期计划。因此,我们希望看到对 JDK 的 macOS/AArch64 端口的广泛需求。
尽管可以通过 macOS 的内置Rosetta 2转换器在基于 AArch64 的系统上运行 JDK 的 macOS/x64 版本,但该翻译几乎肯定会带来显着的性能损失。
怎么做的?
Linux 的 AArch64 端口(JEP 237)已经存在,Windows 的 AArch64 端口(JEP 388)的工作正在进行中。我们希望通过使用条件编译(在 JDK 的端口中很常见)来重用来自这些端口的现有 AArch64 代码,以适应低级约定的差异,例如应用程序二进制接口 (ABI) 和保留的处理器寄存器集。
macOS/AArch64 禁止内存段同时可执行和可写,这一策略称为write-xor-execute(W^X)。HotSpot VM 会定期创建和修改可执行代码,因此此 JEP 将在 HotSpot 中为 macOS/AArch64 实现 W^X 支持。
补充:
JEP 398:弃用 Applet API 以进行删除
1996年在JDK1.0中首次发布Applet
在JAVA 9 中 ,声明 弃用 AppletAPI
在17中进一步弃用或者删除
在未来版本 彻底删除
要在 Web 浏览器中运行 Java 小程序,需要使用浏览器插件,但是所有 Web 浏览器供应商都已取消对 Java 浏览器插件的支持,一旦浏览器插件小时,就没有理由再去使用Applet API了。取而代之的是,Java Web Start 或者可安装的应用程序。
Java 9 中的JEP 289先前已弃用 Applet API,但并未将其删除。
弃用或移除标准 Java API 的这些类和接口:
java.applet.Applet
java.applet.AppletStub
java.applet.AppletContext
java.applet.AudioClip
javax.swing.JApplet
java.beans.AppletInitializer
弃用(删除)引用上述类和接口的任何 API 元素,包括以下中的方法和字段:
java.beans.Beans
javax.swing.RepaintManager
javax.naming.Context
JEP 407 删除RMI激活机制
删除远程方法调用 (RMI) 激活机制,同时保留 RMI 的其余部分。
RMI 激活机制已过时且已废弃。它已被Java SE 15 中的JEP 385弃用。
为什么要删除?
近十几年来,没有应用程序使用RMI 激活机制,也没有新的程序用到它,关于它的最新的JavaRanch论坛问题,还停留在2003年,而该论坛每天会收到数十篇其他主题的帖子。
RMI 激活带来了持续的维护负担,RMI Activation 在 JDK 中有一套测试。这些测试通常调用 RMI 激活服务 ( rmid) 以及客户端和服务器 JVM。这会导致测试运行缓慢,并且这些测试的多进程性质会导致源源不断的间歇性虚假故障。
java.rmi.activation从 Java SE API 规范中删除包
更新RMI 规范以删除提及 RMI 激活
去掉实现RMI激活机制的JDK库代码
删除 RMI 激活机制的 JDK 回归测试
删除 JDK 的rmid激活守护进程及其文档
补充:
JEP 412:外部函数和内存 API(孵化器)
Java 程序可以通过该 API 与 Java 运行时之外的代码和数据进行互操作。通过有效调用外部函数(即 JVM 之外的代码),以及安全地访问外部内存(即不由 JVM 管理的内存),API 使 Java 程序能够调用本地库和处理本地数据,而不是通过JNI。
要做什么?
用更高级的纯粹的Java开发模型,代替JNI,具有更好的易用性。
在性能上,能够媲美JNI和sun.misc.Unsafe
提供对不同类型的外部内存(如,本级内存,持久内存和托管的堆内存)进行操作的方法,并逐渐适配其他平台。
默认禁用不安全的操作,只有在应用程序开发人员或最终用户明确选择后才允许它们。
为什么这么做?
Java 平台一直为希望超越 JVM 并与其他平台交互的库和应用程序开发人员提供丰富的基础。Java API 以方便可靠的方式公开非 Java 资源,无论是访问远程数据 (JDBC)、调用 Web 服务(HTTP 客户端)、服务远程客户端(NIO 通道)还是与本地进程通信(Unix 域套接字)。
但是,Java 开发人员在访问一种重要的非 Java 资源时仍然面临重大障碍:与 JVM 位于同一台机器上但在 Java 运行时之外的代码和数据。
补充:
Java 8服务接口和客户端
从Java 9开始,Java添加了-release 命令行选项, 主要是解决cross-compilation问题,如使用Java 17,通过-release 8编译选项就可以确保编译出的代码能够运行在Java 8之上,如HSF的服务接口或者客户端等可能还是需要进行Java 8兼容。 使用-source 8, -target 8并不能保证一定能够在Java 8上运行,还需要一个-bootclasspath来解决JDK API的问题,而 -release 则是-source, -target and -bootclasspath三者的复合体。 所以使用Java 17后,针对Java 8的兼容,最好添加一下-release的设置,如下:
8
JEP,JSR,JLS三者关系
JEP: JEP提出并发展了实验思想,以至于可以指定它们。 并非所有JEP都能实现。
JSR: JSR采纳了成熟的想法(例如来自JEP的想法),并产生了新的规范或对现有规范的修改,可能成为JLS。 并非所有JSR都能实现。JEP相比JSR更早期
JLS: JSR的研究通常会产生一个标准, 有些会产生规范的接口和一些参考实现(如:JSR-303 Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现). JLS规范只是JAVA规范中的一个部分,其他规范还包含JVM规范,JSP规范,EJB规范等等.
JEP是未来发展规划的列表和建议, 可用于JDK远景路线图. JSR是JCP的产物,JCP过程一般用于开发或修订Java技术规范的正式流程.