当前位置:   article > 正文

从Java8升级到Java17需要了解的新特性和兼容问题_java 17兼容情况

java 17兼容情况


前言

Java 17 是一个长期支持(LTS)版本,于2021年9月14日发布,带来了14个新功能,其中包括文本块、switch表达式、record关键字、sealed classes密封类等语法特性,以及增强的伪随机数生成器、使用新的 macOS 渲染库等性能改进。
最近看到一篇文章,说java 17的用户采用率在一年内增长了430%,但我目前用的都是Java8,所以就顺便了解了Java8过度到Java17需要了解哪些新特性。粗略筛查后主要有以下几个:

  • 模式匹配(JEP 394和JEP 406):这个特性允许在switch语句和表达式中使用模式匹配,以及在instanceof操作符中使用类型模式。模式匹配可以让代码更简洁、更易读、更安全。
  • 文本块(JEP 378):这个特性允许用三个双引号来定义多行的字符串,而不需要拼接或转义。文本块可以保持字符串的格式,方便编写HTML、JSON、SQL等内容。
  • 记录(JEP 395):这个特性允许定义一种特殊的类,它只包含不可变的数据。记录可以自动生成构造器、访问器、equals、hashCode、toString等方法,减少样板代码。
  • 密封类(JEP 409):这个特性允许限制某个类或接口的子类或实现类的数量和类型。密封类可以提高可维护性和安全性,以及支持模式匹配。
  • 局部变量类型推断(JEP 286和JEP 323):这个特性允许在局部变量声明中使用var关键字,让编译器根据初始化表达式推断变量的类型。这样可以减少重复的类型信息,提高可读性。此外,还可以在lambda表达式的参数中使用var关键字,以便添加注解或显式指定泛型类型。
  • 新的垃圾收集器(JEP 318、JEP 346和JEP 379):这些特性分别引入了Epsilon、ZGC和Shenandoah垃圾收集器,它们都是实验性的,目的是为了提高应用程序的性能和吞吐量。Epsilon是一个无操作的垃圾收集器,它只分配内存,不回收内存。ZGC和Shenandoah是两个低延迟的垃圾收集器,它们都使用并发标记-整理算法,可以处理大内存(TB级别)的情况。

特性详解

模式匹配

模式匹配是一种测试对象是否具有特定结构的方法,如果匹配成功,还可以从对象中提取数据。在Java中,我们可以使用instanceof操作符和switch语句来进行模式匹配。例如:

// 使用instanceof操作符进行模式匹配
if (obj instanceof String s) {
  // 如果obj是String类型,就把它赋值给s变量
  System.out.println(s.length());
}

// 使用switch语句进行模式匹配
switch (obj) {
  case Integer i -> System.out.println(i + 1); // 如果obj是Integer类型,就把它赋值给i变量
  case String s -> System.out.println(s.length()); // 如果obj是String类型,就把它赋值给s变量
  default -> System.out.println("Unknown type"); // 其他情况
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

模式匹配可以让代码更简洁、更易读、更安全,因为我们不需要强制类型转换或空指针检查。模式匹配还可以与密封类和记录结合使用,以实现更强大的功能。

文本块

文本块是一种多行字符串字面量,它可以用三个双引号来定义,而不需要拼接或转义。文本块可以保持字符串的格式,方便编写HTML、JSON、SQL等内容。例如:

// 使用文本块定义一个HTML字符串
String html = """
<html>
  <body>
    <p>Hello, world</p>
  </body>
</html>
""";

// 使用文本块定义一个JSON字符串
String json = """
{
  "name": "Alice",
  "age": 25
}
""";

// 使用文本块定义一个SQL字符串
String sql = """
SELECT * FROM users
WHERE name = 'Bob'
""";
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

文本块的结果类型仍然是java.lang.String,只是提供了一种更方便的书写方式。文本块中的缩进和换行符会被编译器处理,以得到合适的输出。文本块中还可以使用转义序列和格式化方法,以实现更灵活的控制。

记录

记录是一种特殊的类,它可以作为不可变数据的透明载体,比普通类更简洁。记录可以看作是具有名字的元组。记录可以自动生成构造器、访问器、equals、hashCode、toString等方法,减少样板代码。记录的特点有以下几个:

记录是一种特殊的类,它只包含不可变的数据。

记录可以自动生成构造器、访问器、equals、hashCode、toString等方法,减少样板代码。

记录的声明方式如下:

record Point(int x, int y) {} // 定义一个记录类Point,包含两个整型字段x和y
  • 1

记录类的使用方式如下:

Point p1 = new Point(1, 2); // 创建一个Point对象p1
Point p2 = new Point(1, 2); // 创建另一个Point对象p2
System.out.println(p1.x()); // 访问p1的x字段,输出1
System.out.println(p1.equals(p2)); // 比较p1和p2是否相等,输出true
System.out.println(p1.toString()); // 输出p1的字符串表示,输出Point[x=1, y=2]
  • 1
  • 2
  • 3
  • 4
  • 5

记录类可以继承接口,也可以重写默认的方法,还可以添加静态成员和嵌套类。

记录类主要用于表示简单的数据载体,例如领域对象或数据传输对象。

记录类还支持以下特性:

紧凑构造器:紧凑构造器是一种没有参数列表和返回类型的构造器,它可以用于对记录组件进行验证或转换,或者执行其他逻辑。例如:

record Point(int x, int y) {
  Point {
    if (x < 0 || y < 0) {
      throw new IllegalArgumentException("Negative coordinates not allowed");
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

派生记录:派生记录是一种使用with关键字从已有的记录对象创建一个新的记录对象,并修改部分组件值的方法。例如:

record Point(int x, int y) {}

Point p1 = new Point(1, 2); // 创建一个Point对象p1
Point p2 = p1.withX(3); // 创建一个派生记录p2,它的x值为3,y值与p1相同
System.out.println(p2); // 输出Point[x=3, y=2]
  • 1
  • 2
  • 3
  • 4
  • 5

密封类

密封类是一种限制某个类或接口的子类或实现类的数量和类型的方法。密封类可以提高可维护性和安全性,以及支持模式匹配。密封类的声明方式如下:

// 使用sealed关键字声明一个密封类Shape,它只能被Circle和Rectangle继承
public sealed class Shape permits Circle, Rectangle {}

// 使用final关键字声明一个最终类Circle,它继承自Shape
public final class Circle extends Shape {
  private final double radius;
  public Circle(double radius) {
    this.radius = radius;
  }
  public double getRadius() {
    return radius;
  }
}

// 使用non-sealed关键字声明一个非密封类Rectangle,它继承自Shape,但可以被其他类继承
public non-sealed class Rectangle extends Shape {
  private final double length;
  private final double width;
  public Rectangle(double length, double width) {
    this.length = length;
    this.width = width;
  }
  public double getLength() {
    return length;
  }
  public double getWidth() {
    return width;
  }
}
  • 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

密封类的使用方式如下:

// 创建一个Shape对象s,它是一个Circle对象
Shape s = new Circle(1.0);

// 使用switch语句进行模式匹配,根据s的实际类型执行不同的操作
switch (s) {
  case Circle c -> System.out.println("Circle with radius " + c.getRadius());
  case Rectangle r -> System.out.println("Rectangle with length " + r.getLength() + " and width " + r.getWidth());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

密封类可以继承接口,也可以实现接口,还可以嵌套在其他类或接口中。密封类主要用于表示有限的、固定的、已知的类型层次结构,例如代数数据类型或状态机。

局部变量类型推断

局部变量类型推断是一种让编译器根据初始化表达式推断变量类型的方法,这样我们就不需要显式地写出变量类型。局部变量类型推断可以使用var关键字来实现,它可以用在局部变量声明中,例如:

// 使用var关键字声明一个局部变量name,它的类型由编译器推断为String
var name = "Alice";

// 使用var关键字声明一个局部变量list,它的类型由编译器推断为List<String>
var list = List.of("a", "b", "c");
  • 1
  • 2
  • 3
  • 4
  • 5

局部变量类型推断可以减少重复的类型信息,提高可读性。它还可以在lambda表达式的参数中使用var关键字,以便添加注解或显式指定泛型类型,例如:

// 使用var关键字声明一个lambda表达式的参数s,并添加@NonNull注解
Function<String, Integer> f = (@NonNull var s) -> s.length();

// 使用var关键字声明一个lambda表达式的参数t,并显式指定泛型类型为List<String>
Consumer<List<String>> c = (var t) -> System.out.println(t.size());
  • 1
  • 2
  • 3
  • 4
  • 5

局部变量类型推断只能用于局部变量,不能用于成员变量、方法参数、返回类型等。它也不能用于没有初始化表达式的变量,或者初始化表达式为null的变量。它也不会改变Java的静态类型系统,只是提供了一种更简洁的书写方式。

新的垃圾收集器

这些特性分别引入了Epsilon、ZGC和Shenandoah垃圾收集器,它们都是实验性的,目的是为了提高应用程序的性能和吞吐量。Epsilon是一个无操作的垃圾收集器,它只分配内存,不回收内存。ZGC和Shenandoah是两个低延迟的垃圾收集器,它们都使用并发标记-整理算法,可以处理大内存(TB级别)的情况。

  • Epsilon垃圾收集器:Epsilon垃圾收集器是一个无操作的垃圾收集器,它只分配内存,不回收内存。它可以用于测试、基准测试、性能分析等场景,以消除垃圾收集器对结果的影响。它也可以用于一些不需要垃圾回收或有自己内存管理机制的应用程序。要使用Epsilon垃圾收集器,需要在启动时指定以下参数:
-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
  • 1
  • ZGC垃圾收集器:ZGC垃圾收集器是一个可扩展的低延迟的垃圾收集器,它使用并发标记-整理算法,可以处理大内存(TB级别)的情况。它的目标是在不牺牲吞吐量或内存占用的情况下,将暂停时间控制在10毫秒以内。它还支持多种压缩技术,以减少内存碎片和地址空间占用。要使用ZGC垃圾收集器,需要在启动时指定以下参数:
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC: 与必应的对话, 2023/5/9
(1) Java 10 Local Variable Type Inference - Oracle. https://developer.oracle.com/learn/technical-articles/jdk-10-local-variable-type-inference.
(2) Java 10 LocalVariable Type-Inference | Baeldung. https://www.baeldung.com/java-10-local-variable-type-inference.
(3) Local Variable Type Inference or LVTI in Java 10 - GeeksForGeeks. https://www.geeksforgeeks.org/local-variable-type-inference-or-lvti-in-java-10/.
(4) Java 10 - Local Variable Type Inference - TutorialsPoint. https://www.tutorialspoint.com/java10/java10_local_variable_type_inference.htm.
(5) Java 10 Local variables Type Inference tutorials with examples. https://www.cloudhadoop.com/java10-local-variables-type-inference/.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • Shenandoah垃圾收集器:Shenandoah垃圾收集器是一个低延迟的垃圾收集器,它使用并发标记-整理算法,可以处理大内存(TB级别)的情况。它的目标是在不牺牲吞吐量或内存占用的情况下,将暂停时间控制在10毫秒以内。它还支持多种压缩技术,以减少内存碎片和地址空间占用。要使用Shenandoah垃圾收集器,需要在启动时指定以下参数:
-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
  • 1

兼容问题

从Java 8换成Java 17,并不能简单的升级,还需要考虑到下面这些容性问题:

  • 模块化:从Java 9开始,Java SE平台被模块化,这意味着一些原来公开的API可能被封装在不同的模块中,导致访问不到。例如,一些框架或库可能使用反射来访问Java内部的API,这在Java 17中默认是不允许的。要解决这个问题,可以使用–add-exports或–add-opens等命令行参数来打开特定模块的特定包,或者使用–illegal-access参数来允许非法的反射访问。
  • 废弃和移除:从Java 9到Java 17,一些工具、组件、API和特性被废弃或移除了,这可能导致编译或运行时出现错误。例如,Applet API、Nashorn JavaScript引擎、CORBA模块、JDK Flight Recorder等都被移除了。要解决这个问题,可以查看每个版本的迁移指南,了解哪些内容被废弃或移除了,以及如何替换它们。
  • 行为变化:从Java 9到Java 17,一些API或特性的行为发生了变化,这可能导致运行时出现不一致或异常。例如,正则表达式匹配、安全管理器、字符编码、垃圾收集器等都有一些行为变化。要解决这个问题,也可以查看每个版本的迁移指南,了解哪些内容发生了行为变化,以及如何适应它们。

总之,从Java 8换成Java 17,并不是一个简单的过程,需要仔细分析和测试你的项目,以及使用的第三方依赖。后面实践后再来分享,如果有好的文章推荐也请在评论区留言,谢谢。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/363225
推荐阅读
相关标签
  

闽ICP备14008679号