Java 17 浅析

简介: 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/d

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特性解析

JEP 395:Records

Java 是基于对象的语言,也就是说,Java 是一种 基于指针的间接引用 的语言。这个基于指针的特性,给每个对象带来了唯一标识性。例如判断两个 Object 的 ==,其实判断的是两个对象的内存相对映射地址是否相同,尽管两个对象的 field 完全一样,他们的内存地址也不同。同时这个特性也给对象带来了 多态性易变性 还有 的特性。


但是,并不是所有对象都需要这种特性。由于 指针与间接访问 带来了性能瓶颈,Java 准备对于不需要以上提到的特性的对象移除这些特性。于是,Record 出现了。


相关资料:


字节码分析

使用IDEA 的 jclasslib插件 可以查看record类编译后的字节码

自动生成

  1. private final field

  2. 全属性构造器

  3. 属性的get方法

  4. hashCode()

  5. toString()

  6. equals()


方法的核心 是 invokedynamic

invokedynamic

Java 最早是一种静态类型语言,即类型检查的主体过程主要是在编译期而不是运行期。

为了兼容动态类型语法,在 Java 7 引入了字节码指令 invokedynamic

是后来 Java 8 的拉姆达表达式以及 var 语法的实现基础。


invokedynamic底层依赖一种动态确定目标方法的机制 MethodHandle


通过MethodHandle可以动态的获取方法进行调用,和 Java Reflection 反射类似,但是为了追求性能效率,需要用 MethodHandle。


Reflection 仅仅是 Java 语言上补充针对反射的实现,并没有考虑效率的问题, 尤其是 JIT 基本无法针对这种反射调用进行有效的优化


MethodHandle 更是像是对于字节码的方法指令调用的模拟,适当使用的话 JIT 也能对于它进行优化。


Record基本用法

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的一些特点

  1. 允许扩展的类必须具有规范的名称,否则编译时会报错。这意味着匿名类和局部类不能成为被指定扩展的类

  2. sealed class 和它允许扩展的类必须在同一个模块中

  3. 每个被允许扩展的类必须直接继承sealed class

  4. 每个被允许扩展的子类必须使用修饰符 final、sealed、non-sealed中的一个

  1. 声明为final,则表示该子类不可进一步扩展

  2. 声明为sealed,可以进一步限制哪些它的子类进行扩展

  3. 声明为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 支持。


补充:

ARMv8-AArch64简述

ARM和X86架构对比



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


补充:Java Applet 介绍


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激活守护进程及其文档


补充:

1.分布式架构基础 Java RMI详解

2.Remote Object Activation

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 运行时之外的代码和数据。


补充:

JNI详解


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技术规范的正式流程.

补充

JAVA 16 与 JAVA17性能对比



相关文章
|
算法 Java
棋盘覆盖问题(Java)
棋盘覆盖问题(Java)
176 0
棋盘覆盖问题(Java)
|
存储 自然语言处理 运维
JAVA问答11
JAVA问答11
101 0
|
缓存 自然语言处理 监控
JAVA问答14
JAVA问答14
95 0
|
存储 消息中间件 负载均衡
JAVA问答6
JAVA问答6
124 0
|
Java
Java常见的坑(二)
你猜上述程序输出的是什么? 是 ABC easy as 123 吗? 你执行了输出操作,你才发现输出的是 ABC easy as [C@6e8cf4c6 ,这么一串丑陋的数字是什么鬼? 实际上我们知道字符串与任何数值的相加都会变为字符串,上述事例也不例外, numbers输出其实实际上是调用了Object.toString()方法,让numbers转变为'[c' + '@' + 无符号的十六进制数。
85 0
|
Java
logYF.java
logYF.java
67 0
|
Oracle IDE Java
从Java8到Java17
背景Java8的前世今生作为一名程序员,笔者从大学开始接触Java这门编程语言,还记得当时的版本是1.4,不过这个版本的寿命还挺长,目前在一些金融行业的存量系统中依然还有1.4版本的影子。从C/C++到Java,Java给我的第一印象,就是这门语言的语法非常之啰嗦,比如内部类,像下面这段排序的代码,在C语言里面可以很方便地通过函数指针来解决,C++可以通过运算符重载来解决,唯独Java语言的写法最
从Java8到Java17
|
Java Linux 索引
Java CoryOnWriteArrayList 实现
本文着重介绍 Java 并发容器中 CoryOnWriteArrayList 的实现方式。
|
前端开发 Java 关系型数据库
JAVA知识
那么问题来了,JAVA为啥需要这么多类加载器(当然是多层负责每层对应的类系统,而且多态这个磨人的妖精很是厉害,需要多层加载机制进行处理。–个人理解)
20402 0
Java8-ConcurrentUtils
import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; public class ConcurrentUtils { public static void s...
864 0