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

Java17是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.

相关推荐
考虑考虑8 小时前
Jpa使用union all
java·spring boot·后端
用户3721574261359 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊10 小时前
Java学习第22天 - 云原生与容器化
java
渣哥12 小时前
原来 Java 里线程安全集合有这么多种
java
间彧12 小时前
Spring Boot集成Spring Security完整指南
java
间彧12 小时前
Spring Secutiy基本原理及工作流程
java
Java水解13 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆15 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学16 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole16 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端