markdown

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

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

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

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

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

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 关键字,但需要使用默认情况。

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 示例类,我们就必须像下面这样定义我们的类:

public class Fruit {
    private String name;
    private int price;

    //getters, setters, equals and hashcode methods
}

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

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 方法。

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

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 类,也可以是扩展类。如果想控制哪些类可以扩展超类,可以将所有类放在同一个包中,并赋予超类包可见性。不过,从包外部访问超类已经不可能了。下面的代码就是一个例子:

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

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

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

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 时对该对象执行某些操作,则需要显式地将该对象转为该特定类。下面是一个示例:

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,我们可以将其改为:

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 进行模式匹配的更多信息,请点击此处。

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 的确切方法调用。

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 方法导致了异常。

// Java 11
Exception in thread "main" java.lang.NullPointerException
        at com.rg.java17.HelpfulNullPointerExceptions.main(HelpfulNullPointerExceptions.java:13)
		
// 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 标准将数字格式化为紧凑、人类可读的形式。有 "短 "和 "长 "两种格式可供选择,示例如下:

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

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

// Output
1K
1 thousand

新增日期间支持

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

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)));

// 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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值