likes
comments
collection
share

探索 Java 17:局部变量类型推断的扩展以及其他新特性

作者站长头像
站长
· 阅读数 4

Java 17 是 JDK 最新版本,引入了多项新功能、增强和优化,包括增强的 Switch 表达式、Sealed 类和接口、局部变量类型推断的扩展、增强的垃圾回收机制、Vector API 等等。本文将会和Java 8 的代码进行对比,讨论其优劣势和实际应用。除此之外,我们还将概述 Java 17 的其他新特性,以帮助您更好地理解和应用 Java 17

Java 17 概述

Java 17 的主要目标是改进 Java 的生产力和开发效率,并增强 Java 的安全性和可维护性。它引入了多项新功能、增强和优化,包括:

  • 局部变量类型推断的扩展
  • 增强的 Switch 表达式
  • Sealed 类和接口
  • 动态类文件常量
  • 增强的垃圾回收机制
  • 改进的 ZGC(Z Garbage Collector)
  • Vector API
  • JVM 统一日志系统
  • Java 17的劣势

Java 17 还包括一些低级别的增强和优化,如 OpenSSL 的默认加密套件、升级至 Unicode 13、类嵌套限制的放宽等等。

Java 17 新特性详解

Java 17 引入了许多新特性,以下是一些新特性的详细介绍:

局部变量类型推断

局部变量类型推断是在 Java 10 中引入的一个功能,它允许您在声明变量时使用 var 关键字,而不必显式地指定变量的类型。Java 17 扩展了这一功能,允许在方法引用的上下文中使用 var 关键字。

这意味着您可以使用 var 关键字来推断方法的参数和返回类型。

例如,在 Java 8 中,您可能需要编写以下代码来计算一个字符串列表中所有字符串的总长度:

List<String> list = Arrays.asList("Java", "is", "fun");
int totalLength = 0;
for (String s : list) {
    totalLength += s.length();
}
System.out.println("Total length: " + totalLength);

但在 Java 17 中,您可以使用局部变量类型推断的扩展来编写更简洁的代码:

var list = Arrays.asList("Java", "is", "fun");
var totalLength = list.stream().mapToInt(String::length).sum();
System.out.println("Total length: " + totalLength);

如您所见,这些代码相比于 Java 8 更简洁易读。然而,需要注意的是,在某些情况下,使用 var 关键字可能会降低代码的可读性,因此应该谨慎使用。

增强的 Switch 表达式

在 Java 12 中引入了 Switch 表达式,它允许您使用更简洁的语法编写 Switch 语句。Java 17 增强了 Switch 表达式,包括新的 case 标签、可匹配的模式和箭头运算符等。

新的 case 标签允许您将多个 case 标签放在同一行中,并使用逗号分隔。例如:

int num = 2;
switch (num) {
    case 1, 2, 3 -> System.out.println("Number is 1, 2 or 3");
    case 4 -> System.out.println("Number is 4");
    default -> System.out.println("Number is neither 1, 2, 3 nor 4");
}

可匹配的模式允许您在 case 标签中使用模式匹配。例如:

Object obj = "Hello, World!";
switch (obj) {
    case String s && s.length() > 10 -> System.out.println("String is long");
    case String s -> System.out.println("String is " + s);
    case Integer i -> System.out.println("Integer is " + i);
    default -> System.out.println("Object is of an unexpected type");
}

箭头运算符允许您使用表达式或语句作为 Switch 分支的返回值。例如:

int num = 2;
String result = switch (num) {
    case 1 -> "Number is 1";
    case 2 -> "Number is 2";
    case 3 -> "Number is 3";
    default -> {
        System.out.println("Number is not 1, 2 or 3");
        yield "Number is not 1, 2 or 3";
    }
};
System.out.println(result);

在这个示例中,我们使用 Switch 表达式来返回一个字符串,根据 num 的值不同返回不同的字符串。如果 num 的值不是 1、2 或 3,则打印一条消息并返回一个默认字符串。

Switch 表达式的这些增强功能可以使代码更简洁、易读,同时也提高了代码的可维护性和灵活性。

Sealed 类和接口

Java 17 引入了 Sealed 类和接口,这是一个重要的功能,可以使类和接口更加安全和可扩展。Sealed 类和接口可以限制它们的子类的数量和类型,从而提高了代码的安全性和可扩展性。

在 Java 8 中,您可以创建一个接口并声明它的方法,然后让其他类实现这个接口。这意味着您不能控制实现接口的类的数量和类型。在 Java 17 中,您可以使用 Sealed 接口来限制实现接口的类的数量和类型。

下面是一个示例:

public sealed interface Shape permits Circle, Rectangle, Triangle {
    double area();
}

在这个示例中,我们声明了一个 Sealed 接口 Shape,并使用 permits 关键字指定了 Shape 接口的子类。这意味着只有 Circle、Rectangle 和 Triangle 类可以实现 Shape 接口。如果尝试实现 Shape 接口的其他类将引发编译时错误。

Sealed 类和接口可以帮助您控制类的层次结构,提高代码的可维护性和可扩展性。它们还可以帮助您编写更安全的代码,因为只有受信任的类可以实现 Sealed 类和接口。

在 Java 8 中,我们可以声明一个接口,并让多个类实现它,如下所示:

public interface Shape {
    double area();
}

public class Rectangle implements Shape {
    private double length;
    private double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public double area() {
        return length * width;
    }
}

public class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

在这个示例中,我们定义了一个 Shape 接口,并让 Rectangle 和 Circle 类实现它。这种设计方式可以让我们在代码中轻松地引用不同形状的对象,例如:

List<Shape> shapes = Arrays.asList(new Rectangle(10, 20), new Circle(5));
double totalArea = shapes.stream().mapToDouble(Shape::area).sum();
System.out.println("Total area: " + totalArea);

尽管在 Java 8 中,我们可以通过接口实现多态,但是这种设计方式也存在一些问题。例如,我们不能控制哪些类可以实现该接口,因此可能会导致意外的结果。此外,如果我们想要添加新的实现类,我们需要修改该接口的定义,并重新编译所有实现类。

在 Java 17 中,我们可以使用 Sealed 类和接口来解决这些问题。下面是一个示例:

public sealed interface Shape permits Rectangle, Circle {
    double area();
}

public final class Rectangle implements Shape {
    private double length;
    private double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public double area() {
        return length * width;
    }
}

public final class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

在这个示例中,我们使用了 sealed 关键字来修饰 Shape 接口,并使用 permits 关键字列出了 Rectangle 和 Circle 类作为该接口的实现类。这样,我们可以控制哪些类可以实现 Shape 接口,并可以确保不会有任何未知的实现类。

此外,我们还可以使用 Sealed 类和接口来限制类或接口的扩展或实现。这种方式可以提高代码的可读性和可维护性,并使代码更加安全。

例如,我们可以将 Shape 接口定义为 non-sealed,这样只有在特定模块内才能使用该接口。假设我们有一个 Shape 模块,我们可以定义以下接口:

public non-sealed interface Shape {
    double area();
}

在该模块中,我们可以定义任意数量的实现类,例如:

public final class Rectangle implements Shape {
    private double length;
    private double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public double area() {
        return length * width;
    }
}

如果我们想要在另一个模块中定义新的实现类,我们需要在 Shape 模块中显式地将该类列为 permitted subclass,例如:

public sealed interface Shape permits Circle, Square {
    double area();
}

public final class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

在这个示例中,我们将 Circle 类添加到 permitted subclass 列表中,并使用 Square 类来演示其他模块中的非允许实现。

假设我们在另一个模块中定义以下 Square 类:

public final class Square implements Shape {
    private double side;

    public Square(double side) {
        this.side = side;
    }

    @Override
    public double area() {
        return side * side;
    }
}

由于 Square 类不在 permitted subclass 列表中,因此编译器会产生错误。这样,我们可以确保只有在 Shape 模块中列出的类才能实现 Shape 接口,从而增强了代码的安全性和可维护性。

总之,Sealed 类和接口是 Java 17 中一个非常实用的特性,它可以提高代码的可读性、可维护性和安全性,而且与 Java 8 相比,使用 sealed 类和接口可以更好地控制类或接口的扩展和实现。

动态类文件常量

动态类文件常量是 Java 17 中的一项新特性,它允许在运行时动态生成常量值。在此之前,Java 中的常量值只能在编译时确定,无法在运行时进行修改。动态类文件常量是通过 Constant Dynamic 类型来实现的,这个类型代表了一个在运行时计算的常量值。

让我们来看一下一个例子,首先是 Java 8 中的代码:

public class DynamicConstantExample {
    public static final String CONSTANT = "CONSTANT_VALUE";
    public static void main(String[] args) {
        System.out.println(CONSTANT);
    }
}

在 Java 8 中,常量值必须在编译时确定,因此我们只能使用静态 final 字段来定义常量。这个例子中的 CONSTANT 字段是一个 final 字符串,它的值在编译时就已经确定了。当我们运行这个程序时,它会输出 "CONSTANT_VALUE"。

现在,让我们来看一下使用动态类文件常量的 Java 17 代码:

import java.lang.invoke.*;

public class DynamicConstantExample {
    public static void main(String[] args) throws Throwable {
        CallSite constant = 
            new ConstantCallSite(MethodHandles.constant(String.class, "CONSTANT_VALUE"));
        String value = (String) constant.getTarget().invokeExact();
        System.out.println(value);
    }
}

增强的垃圾回收机制

Java 17 引入了一些新的垃圾回收机制的增强,旨在提高性能和减少停顿时间。在此之前,让我们先了解一下 Java 的垃圾回收机制。

Java 中的垃圾回收是通过检测不再被引用的对象来进行的。当一个对象不再被引用时,它就成为了垃圾。垃圾回收器会定期运行,并检查哪些对象是垃圾,然后将其释放。这种自动内存管理机制使 Java 程序员能够避免手动管理内存的复杂性,从而提高开发效率和代码可靠性。

在 Java 17 中,垃圾回收机制有了一些增强,主要包括以下几个方面:

  1. 基于 Epsilon 的垃圾回收器:Epsilon 是一种实验性的垃圾回收器,旨在提供一种不执行任何实际垃圾回收的选项,从而最大化应用程序的吞吐量和延迟。这个选项适用于那些需要减少垃圾回收的系统,如 Spark 和 Hadoop。
  2. 基于 ZGC 的可选 Epsilon 支持:在 Java 17 中,Epsilon 垃圾回收器可以与 ZGC 垃圾回收器结合使用,从而在某些场景下提高性能。例如,在具有大量内存和高吞吐量要求的应用程序中,使用 ZGC+Epsilon 可以提高 GC 的吞吐量和延迟。
  3. 基于 G1 的可选 JDK 日志标记:在 Java 17 中,可以使用基于 G1 的可选 JDK 日志标记来跟踪垃圾回收过程中对象的移动情况,从而帮助开发人员优化应用程序的性能。
  4. 基于 C++ 的并发垃圾回收器:在 Java 17 中,引入了一个新的垃圾回收器,它是基于 C++ 的并发垃圾回收器。这个新的回收器使用多线程技术来提高垃圾回收的效率和性能,并减少应用程序的停顿时间。

总的来说,Java 17 引入了一些新的垃圾回收机制的增强,可以提高垃圾回收的效率和性能,并减少应用程序的停顿时间。这些增强对于大规模的 Java 应用程序和需要高性能和低延迟的应用程序特别有用。与 Java 8 相比,这些增强使得 Java 垃圾回收机制更加灵活和可定

改进的 ZGC(Z Garbage Collector)

ZGC(Z Garbage Collector)是 JDK 11 中引入的一种低延迟垃圾回收器。Java 17 进一步改进了 ZGC,增加了一些新的特性和优化,使得它在处理大型内存和高并发负载方面表现更加优异。

ZGC 的主要优点是,它可以处理非常大的堆内存,甚至超过了 16TB。同时,它的垃圾回收暂停时间非常短,可以控制在几毫秒内。这对于对响应时间有严格要求的应用程序非常有帮助。

在 Java 17 中,ZGC 支持在 Windows 和 macOS 系统上使用,之前只能在 Linux 上使用。此外,Java 17 还增加了一些新的配置选项,可以进一步优化垃圾回收器的行为。

总之,ZGC 是一个非常强大的垃圾回收器,可以帮助开发人员处理大型内存和高并发负载,同时还能保证低延迟和高吞吐量。如果你的应用程序需要处理大量的数据,并且需要快速响应用户请求,那么 ZGC 可能是一个非常好的选择。

Vector API

Vector API 是 Java 17 中的一个新特性,它允许开发人员以向量化的方式对数组进行操作,从而提高代码的性能和效率。它可以在现代的 CPU 上使用 SIMD(Single Instruction Multiple Data)指令集来并行处理数据,大大提高了数据处理的速度。

在 Java 17 中,Vector API 提供了一组基本的操作函数,包括加、减、乘、除等基本运算,以及一些高级函数,比如求平方根、对数等。这些函数可以对单个数组元素进行操作,也可以对整个数组进行操作。

Vector API 的一个重要特点是,它是可移植的。它可以在不同的 CPU 上运行,并且会自动适应不同的 SIMD 指令集,从而最大程度地提高代码的性能。

Vector API 的使用需要一定的编程技巧和经验。需要使用 Vector API 的开发人员需要了解向量数据类型和向量寄存器的概念,并且需要注意一些限制和约束,比如向量长度必须是 2、4、8、16 或 32 的倍数等。

总之,Vector API 是 Java 17 中一个非常有用的新特性,它可以帮助开发人员更加高效地处理大量的数据,并且提高代码的性能和效率。但是,使用 Vector API 需要一定的编程技巧和经验,需要开发人员仔细研究和了解相关的知识。因为笔者也只是做了粗略的了解,这里就不赘述了。

JVM 统一日志系统

Java 17 中引入了一个 JVM 统一日志系统,该系统为开发人员提供了一种标准化的方式来记录 JVM 日志。在以前的版本中,JVM 日志是由不同的组件各自记录的,使用不同的格式和参数,因此很难进行统一管理和分析。而现在,统一日志系统可以将所有的 JVM 日志信息收集到一个地方,并且以统一的格式和参数进行记录,方便开发人员进行管理和分析。

JVM 统一日志系统的实现基于 OpenJDK 中的 JEP 158(Unified Logging),它将所有的日志信息收集到一个单独的日志文件中,该文件可以包含控制台输出、GC 日志、代码缓存信息、线程信息等各种类型的日志信息。开发人员可以通过简单的配置来控制日志的级别、格式和输出方式。

JVM 统一日志系统的好处是显而易见的。首先,它可以帮助开发人员更好地理解 JVM 的内部运行机制,从而更好地进行代码调试和优化。其次,它可以提高生产环境中的问题定位和排查效率,方便快速解决问题。此外,由于日志信息都被统一记录,因此可以更方便地进行日志分析和监控,从而帮助开发人员更好地了解应用程序的运行情况和性能瓶颈。

总之,JVM 统一日志系统是 Java 17 中一个非常有用的新特性,它可以帮助开发人员更好地管理和分析 JVM 日志信息,从而提高代码调试和优化效率,提高生产环境中的问题定位和排查效率,以及更好地了解应用程序的运行情况和性能瓶颈。

Java 17的劣势

  1. 升级成本:尽管Java 17带来了许多新特性和改进,但对于那些已经使用其他版本Java的企业来说,升级到Java 17可能需要一些成本,包括人力成本和代码更新成本等。
  2. 兼容性问题:尽管Java 17保持了向后兼容性,但一些旧版Java应用程序可能需要进行修改才能与Java 17兼容。
  3. 需要更高的硬件要求:Java 17的一些新特性需要更高的硬件要求,包括更多的内存和处理器资源等。因此,对于一些低端硬件的设备来说,可能需要进行升级才能支持Java 17。
转载自:https://juejin.cn/post/7233261440499843131
评论
请登录