Java 17 & Java 11:新功能探索与改进措施知多少?

7是Java编程语言的最新 LTS(长期支持)版本,于 2021年9月14日发布。如果您目前使用的是Java11,那么也许是时候考虑迁移到 Java 17啦,方便我们体验新功能以及了解新版本的改善措施。在本文中,我们将讨论 Java 17 中的新功能,尽管此处讨论的某些功能是在Java 11升级到Java 17的版本中引入的。

【参考文献】

文章:Java 17 vs Java 11: Exploring the Latest Features and Improvements

作者:Rahul Gite

上述译文仅供参考,具体内容请查看上面链接,解释权归原作者所有。

【关于TalkX】

TalkX是一款基于GPT实现的IDE智能开发插件,专注于编程领域,是开发者在日常编码中提高编码效率及质量的辅助工具,TalkX常用的功能包括但不限于:解释代码、中英翻译、性能检查、安全检查、样式检查、优化并改进、提高可读性、清理代码、生成测试用例、有趣的图表生成以及语音助手托克斯等。

TalkX建立了全球加速网络,不需要考虑网络环境,响应速度快,界面效果和交互体验更流畅。并为用户提供了Open AI的密钥,不需要自备ApiKey,不需要自备账号,不需要魔法。

TalkX产品支持:JetBrains (包括 IntelliJ IDEA、PyCharm、WebStorm、Android Studio)、HBuilder、VS Code、Goland.

为什么要从 Java 11 迁移?

虽然 Java 11 也是一个 LTS 版本,并且被许多应用程序所使用,但我们可能会出于一些主要原因而转向 Java 17。

结束对 Java 11 的支持:Java 11 的支持将持续到 2023 年 9 月,扩展支持将持续到 2026 年 9 月。这意味着支持结束后,我们将没有补丁(甚至没有安全补丁)。

Spring 6:Spring 的最新版本 Spring 6 将需要 Java 17 才能运行,由于有许多库与它们一起工作,因此它们也将迁移到 Java 17。如果您的应用程序依赖于 Spring 框架,您一定要考虑迁移到 Java 17。

Java 17 的免费 Oracle JDK:Java 17 是根据新的 NFTC(甲骨文不收费条款和条件)许可证发布的。因此,在生产和商业用途中再次允许免费使用 Oracle JDK 版本(Java 11 则不允许)。

Java 17 有哪些新功能?

Java 17 引入了多项改进和新功能,这些功能将得到长期支持。

文本块(Text Blocks)

Java 引入了文本块,使代码更具可读性,并避免了不必要的字符串格式化。现在,我们可以将文本放在三引号之间,并在其中包含多个双引号字符串,而无需使用转义字符。下面是一个示例:

 

arduino

复制代码

private static void jsonBlock() { String text = """ { "name": "John Doe", "age": 45, "address": "Doe Street, 23, Java Town" } """; System.out.println(text); }

正如我们所看到的,这使得编写需要大量使用转义字符的 Json 和类似字符串变得非常容易。

此外,结尾的三个双引号表示文本块的开始或输出中的缩进。在上面的示例中,由于双引号的位置是在最后一个字符后两个空格,因此输出中的每一行都有两个空格。

引入了两个新的转义字符,用于在文本块内部使用,"\s "用于添加空格,""用于删除换行。在编写长 SQL 语句时特别有用。

 

ini

复制代码

private static void sqlStatement() { String sql = """ SELECT id, firstName, lastName\s\ FROM Employee WHERE departmentId = "IT" \ ORDER BY lastName, firstName"""; System.out.println(text); }

改进的开关语句

开关表达式允许您从开关情况下返回值,并在赋值等中使用这些返回值。Java 允许使用运算符->(箭头)而不是:(冒号)来表示返回表达式。在此表达式中使用 switch 返回时不需要 break 关键字,但需要使用默认情况。

 

arduino

复制代码

private static void improvedSwitch(Fruit fruit) { String text = switch (fruit) { case APPLE, PEAR -> { System.out.println("the given fruit was: " + fruit); yield "Common fruit"; } case ORANGE, AVOCADO -> "Exotic fruit"; default -> "Undefined fruit"; }; System.out.println(text); }

如果在 switch 用例中进行了多个操作,我们可以使用一个用例块,并使用 yield 关键字表示返回值。这里的 yield 是一个上下文相关的关键字,也就是说,你可以在函数的其他地方使用变量名 yield。

‘record’ Type

record 类是一种特殊的不可变类,用于替代数据传输对象(DTO)。通常,如果我们想在类或方法中使用 POJO,就必须在声明类的同时定义所有的获取器、设置器、等价和哈希码函数。例如,要在其他地方使用 Fruit 示例类,我们就必须像下面这样定义我们的类:

 

arduino

复制代码

public class Fruit { private String name; private int price; //getters, setters, equals and hashcode methods }

虽然我们可以通过使用 lombok 等库减少大部分模板代码,但我们仍可以借助记录进一步减少模板代码。有了记录,同样的代码就变成了。

 

arduino

复制代码

public static void doSomething() { record Fruit(String name, int price) {} Fruit fruit = new Fruit("Apple", 100); System.out.println(fruit.getPrice()); }

正如我们所见,我们甚至可以定义本地记录对象的方法。记录对象会自动为其所有字段提供 getter、setter、equals 和 hashcode 方法。

记录内部的字段不能更改,只能由声明记录时给出的参数定义,如上图所示(但我们可以定义静态变量)。我们还可以定义一个自定义构造函数来验证字段。建议不要覆盖记录的获取器和设置器,否则会影响记录的不变性。下图是一个包含多个构造函数、静态变量和方法的记录示例:

 

csharp

复制代码

public record Employee(int id, String firstName, String lastName) { static int empToken; // Compact Constructor public Employee { if (id < 100) { throw new IllegalArgumentException( "Employee Id cannot be below 100."); } if (firstName.length() < 2) { throw new IllegalArgumentException( "First name must be 2 characters or more."); } } // Alternative Constructor public Employee(int id, String firstName) { this(id, firstName, null); } // Instance methods public void getFullName() { if (lastName == null) System.out.println(firstName()); else System.out.println(firstName() + " " + lastName()); } // Static methods public static int generateEmployeeToken() { return ++empToken; } }

记录类的其他一些特点是

  1. 可以在记录中使用嵌套类和接口。
  2. 您也可以拥有嵌套记录,这些记录将隐含为静态记录。
  3. 记录可以实现接口
  4. 可以创建通用记录类
  5. 记录可序列化。

有关记录的更多信息,请点击此处:

密封 "类

sealed 类可以让我们更好地控制哪些类可以扩展我们的类。在 Java 11 中,类可以是 final 类,也可以是扩展类。如果想控制哪些类可以扩展超类,可以将所有类放在同一个包中,并赋予超类包可见性。不过,从包外部访问超类已经不可能了。下面的代码就是一个例子:

 

scala

复制代码

public abstract class Fruit { } public final class Apple extends Fruit { } public final class Pear extends Fruit { }

 

scala

复制代码

private static void problemSpace() { Apple apple = new Apple(); Pear pear = new Pear(); Fruit fruit = apple; class Avocado extends Fruit {}; }

在这里,我们不能阻止 Avocado 扩展 Fruit 类。如果我们将 Fruit 类设为默认类,那么将 apple 赋值给 fruit 对象将无法编译。因此,现在我们可以使用密封类,只允许特定的类扩展我们的超类。下面是一个例子:

 

scala

复制代码

public abstract sealed class FruitSealed permits AppleSealed, PearSealed { } public non-sealed class AppleSealed extends FruitSealed { } public final class PearSealed extends FruitSealed { }

正如我们所看到的,我们使用新的关键字 sealed 来表示这是一个密封类。我们使用 permits 关键字定义了可以扩展的类。任何对密封类进行扩展的类都可以像 PearSealed 一样是最终类,或者像 AppleSealed 一样,通过在声明类时使用非密封关键字,可以被其他类扩展。

这种实现方式允许 AppleSealed 被分配给 FruitSealed 类,但不允许任何未使用 permits 关键字定义的其他类扩展 FruitSealed 类。有关密封类的更多信息,请点击此处。

使用 "instance of "进行模式匹配

在 Java 11 中,我们通常使用 instance of 操作符来检查对象是否属于某个类。如果我们想在实例检查返回 true 时对该对象执行某些操作,则需要显式地将该对象转为该特定类。下面是一个示例:

 

java

复制代码

private static void oldStyle() { Object o = new Grape(Color.BLUE, 2); if (o instanceof GrapeClass) { Grape grape = (Grape) o; System.out.println("This grape has " + grape.getPits() + " pits."); } }

在这里,我们需要显式地将对象转换为葡萄类型,然后找出葡萄果核的数量。有了 Java 17,我们可以将其改为:

 

typescript

复制代码

private static void patternMatchingInJava17() { Object o = new Grape(Color.BLUE, 2); if (o instanceof Grape grape) { System.out.println("This grape has " + grape.getPits() + " pits."); } }

我们可以将校验实例与 &&(和)条件配对,但不能与 ||(或)条件配对,因为在 "或 "条件下,即使校验实例返回 false,语句也可以到达另一个条件。

如果检查实例返回 true,变量 grape 的作用域甚至可以超出 if 代码块。在下面的示例中,如果对象不是 Grape 类型,将抛出 Runtime Exception,因此编译器在执行打印语句时可以确定葡萄对象应该存在。有关使用 instance of 进行模式匹配的更多信息,请点击此处。

 

typescript

复制代码

private static void patternMatchingScopeException() { Object o = new Grape(Color.BLUE, 2); if (!(o instanceof Grape grape)) { throw new RuntimeException(); } System.out.println("This grape has " + grape.getPits() + " pits."); }

有用的 NullPointerException

在 Java 11 中,当我们收到 NullPointerException 时,我们只能获得发生异常的行号,但无法获得解析为空的方法或变量。Java 17 改进了消息传递方式,NullPointerException 消息还会告诉我们导致 NullPointerException 的确切方法调用。

 

typescript

复制代码

public static void main(String[] args) { HashMap<String, Grape> grapes = new HashMap<>(); grapes.put("grape1", new GrapeClass(Color.BLUE, 2)); grapes.put("grape2", new GrapeClass(Color.white, 4)); grapes.put("grape3", null); var color = ((Grape) grapes.get("grape3")).getColor(); }

正如我们在这里看到的,我们正试图获取 "grape3 "对象的颜色,而该对象是空的。当我们比较 Java 11 和 Java 17 中的错误信息时,我们可以看到错误信息的不同之处,因为现在我们可以清楚地知道,对映射中的空对象调用 get 方法导致了异常。

 

php

复制代码

// Java 11 Exception in thread "main" java.lang.NullPointerException at com.rg.java17.HelpfulNullPointerExceptions.main(HelpfulNullPointerExceptions.java:13)

 

kotlin

复制代码

// Java 17 Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.rg.java17.Grape.getColor()" because the return value of "java.util.HashMap.get(Object)" is null at com.rg.java17.HelpfulNullPointerExceptions.main(HelpfulNullPointerExceptions.java:13)

更多改进

支持紧凑的数字格式

为 NumberFormat 类添加了一个工厂方法,以便根据 Unicode 标准将数字格式化为紧凑、人类可读的形式。有 "短 "和 "长 "两种格式可供选择,示例如下:

 

ini

复制代码

NumberFormat shortFormat = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.SHORT); System.out.println(shortFormat.format(1000))

 

ini

复制代码

NumberFormat longFormat = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.LONG); System.out.println(shortFormat.format(1000))

 

arduino

复制代码

// Output 1K 1 thousand

新增日期间支持

DateTime 模式添加了新模式 "B",允许指定一天中的时间。

 

less

复制代码

DateTimeFormatter timeOfDayFomatter = DateTimeFormatter.ofPattern("B"); System.out.println(timeOfDayFomatter.format(LocalTime.of(8, 0))); System.out.println(timeOfDayFomatter.format(LocalTime.of(13, 0))); System.out.println(timeOfDayFomatter.format(LocalTime.of(20, 0))); System.out.println(timeOfDayFomatter.format(LocalTime.of(23, 0))); System.out.println(timeOfDayFomatter.format(LocalTime.of(0, 0)));

 

arduino

复制代码

// Output in the morning in the afternoon in the evening at night midnight

性能基准

与 Java 11 相比,Java 17 在内存使用和时间复杂性方面也有所改进。其中一项基准测试通过让两个版本的代码完成一系列任务,对它们的性能进行了统计。完整的结果和任务描述可以在这里找到。

其中一些一般结果如下

  1. 在 G1GC(默认垃圾回收器)方面,Java 17 比 Java 11 快 8.66%,比 Java 16 快 2.41%。

  2. 对于 ParallelGC(并行垃圾回收器),Java 17 比 Java 11 快 6.54%,比 Java 16 快 0.37%。

  3. 并行垃圾回收器(Java 17 中可用)比 G1 垃圾回收器(Java 11 中使用)快 16.39%。

从 Java 11 迁移到 Java 17 可以带来很多好处,包括新功能和更高的性能。但是,必须注意迁移过程中可能出现的潜在瓶颈。因此,如果我们在项目中使用外部库,就应该格外小心。了解这些潜在问题并采取必要措施加以解决,就能确保顺利成功地迁移到 Java 17!

⚠️:文章翻译上如有语法不准确或者内容纰漏,欢迎各位评论区指正。

【关于TalkX】

TalkX是一款基于GPT实现的IDE智能开发插件,专注于编程领域,是开发者在日常编码中提高编码效率及质量的辅助工具,TalkX常用的功能包括但不限于:解释代码、中英翻译、性能检查、安全检查、样式检查、优化并改进、提高可读性、清理代码、生成测试用例、有趣的图表生成以及新增语音助手托克斯等。

TalkX建立了全球加速网络,不需要考虑网络环境,响应速度快,界面效果和交互体验更流畅。并为用户提供了Open AI的密钥,不需要自备ApiKey,不需要自备账号,不需要魔法。

TalkX产品支持:JetBrains (包括 IntelliJ IDEA、PyCharm、WebStorm、Android Studio)、HBuilder、VS Code、Goland.

  • 21
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值