Java 11 新特性

Java 11 是 JDK 的第一个长期支持版本,带来了许多新特性,如 Lambda 参数的局部变量语法、HTTP 客户端、新的 String 和文件方法。其中,HTTP 客户端简化了网络操作,Epsilon 垃圾收集器适用于特殊场景,启动单文件源代码程序则简化了小型 Java 程序的执行流程。此外,Java 飞行记录器成为标准工具,提供运行时分析,而低开销的堆分析则增强了内存问题的诊断能力。
摘要由CSDN通过智能技术生成

概述

在 Java 11 中,自 2018 年 9 月 25 日发布了切换到六个月发布周期以来 JDK 的第一个长期支持 (LTS) 版本。“长期支持”意味着 Oracle 将为该版本提供安全性补丁好几年了。

Java 11 之前的一个 LTS 版本是 Java 8,所以 Java 9 和 10 不是 LTS 版本,这意味着对这些版本的支持在每个后续版本中都停止了。

根据与开发人员的相关性,我们对 Java 11 中的新特性进行了重写整理讲解。首先是语言本身的变化。其次是对 JDK 类库、工具和实验特性的增强。 最后,弃用、删除和其他细微更改。

同样重要的是要知道,从版本 11 开始,Oracle JDK 只能由开发人员免费使用。公司需要与 Oracle 签订有偿支持合同。另一方面,OpenJDK 11 可供所有人免费使用。

Lambda 参数的局部变量语法 (JEP 323)

JEP 323 允许在隐式类型 lambda 表达式的参数中使用“var”。

什么是隐式类型的 lambda 表达式?

让我们从显式类型的 lambda 表达式开始。在以下示例中,显式表示指定了 lambda 参数 l 和 s 的数据类型,即 List 和 String:

(List<String> l, String s) -> l.add(s);

但是,编译器也可以从上下文派生类型,因此也允许使用以下 - 隐式类型 - 表示法:

(List<String> l, String s) -> l.add(s);

从 Java 11 开始,我们可以使用 Java 10 中引入的“var”关键字来代替显式类型:

(var l, var s) -> l.add(s);

但是当你可以完全省略类型时,为什么要写“var”,就像前面的例子一样?

原因是注解。如果要对变量进行注解,则必须将注解放在类型上——显式类型或“var”。不允许在变量名上放置注释。

如果你想在上面的例子中注解变量,到目前为止只有下面的符号是可能的:

(@Nonnull List<String> l, @Nullable String s) -> l.add(s);

Java 11 现在还允许使用“var”:

(@Nonnull var l, @Nullable var s) -> l.add(s);

不同的符号不得混用。这意味着我们必须为所有变量指定类型,省略所有类型,或者对所有变量使用“var”。

我们最终选择哪种形式取决于特定情况下的可读性和团队的风格。

HTTP 客户端(标准)(JEP 321)

在 Java 11 之前,使用原生 JDK 资源,例如通过 HTTP POST 发送数据需要大量代码。

(以下示例使用 Java 8 中添加的 BufferedReader.lines() 将响应作为 Stream 读取并使用收集器将其组合为 String。在 Java 8 之前,这需要多行。)

public String post(String url, String data) throws IOException {
  URL urlObj = new URL(url);
  HttpURLConnection con = (HttpURLConnection) urlObj.openConnection();
  con.setRequestMethod("POST");
  con.setRequestProperty("Content-Type", "application/json");

  // Send data
  con.setDoOutput(true);
  try (OutputStream os = con.getOutputStream()) {
    byte[] input = data.getBytes(StandardCharsets.UTF_8);
    os.write(input, 0, input.length);
  }

  // Handle HTTP errors
  if (con.getResponseCode() != 200) {
    con.disconnect();
    throw new IOException("HTTP response status: " + con.getResponseCode());
  }

  // Read response
  String body;
  try (InputStreamReader isr = new InputStreamReader(con.getInputStream());
      BufferedReader br = new BufferedReader(isr)) {
    body = br.lines().collect(Collectors.joining("n"));
  }
  con.disconnect();

  return body;
}

JDK 11 包括新的 HttpClient 类,它简化了 HTTP 的使用。

我们可以使用 HttpClient 编写更短且更优雅的代码:

public String post(String url, String data) throws IOException, InterruptedException {
  HttpClient client = HttpClient.newHttpClient();

  HttpRequest request =
      HttpRequest.newBuilder()
          .uri(URI.create(url))
          .header("Content-Type", "application/json")
          .POST(BodyPublishers.ofString(data))
          .build();

  HttpResponse<String> response = client.send(request, BodyHandlers.ofString());

  if (response.statusCode() != 200) {
    throw new IOException("HTTP response status: " + response.statusCode());
  }

  return response.body();
}

新的 HttpClient 类用途广泛:

  • 在上面的示例中,我们通过 BodyPublishers.ofString() 发送一个字符串。使用同一个类的 ByteArray()ofByteArrays()ofFile()ofInputStream()方法,我们可以从其他各种来源读取要发送的数据。
  • 类似地,我们使用其 ofString() 方法以字符串形式获取响应的 BodyHandlers 类也可以返回字节数组和流或将接收到的响应存储在文件中。
  • 与之前的解决方案不同,HttpClient 还支持 HTTP/2 和 WebSocket。
  • 此外,除了上面展示的同步编程模型之外,HttpClient 还提供了一个异步模型。 HttpClient.sendAsync() 方法返回一个 CompletableFuture,然后我们可以使用它来继续异步工作。

例如,post 方法的异步变体可能如下所示:

public void postAsync(
    String url, String data, Consumer<String> consumer, IntConsumer errorHandler) {
  HttpClient client = HttpClient.newHttpClient();

  HttpRequest request =
      HttpRequest.newBuilder()
          .uri(URI.create(url))
          .header("Content-Type", "application/json")
          .POST(BodyPublishers.ofString(data))
          .build();

  client
      .sendAsync(request, BodyHandlers.ofString())
      .thenAccept(
          response -> {
            if (response.statusCode() == 200) {
              consumer.accept(response.body());
            } else {
              errorHandler.accept(response.statusCode());
            }
          });
}

新的 Collection.toArray() 方法 (JDK-8060192)

在 Java 11 之前,Collection 接口提供了两个 toArray() 方法来将集合转换为数组。以下示例以字符串列表为例,展示了这两种方法(以及第二种方法的两种不同用法):

List<String> list = List.of("foo", "bar", "baz");

Object[] strings1 = list.toArray();

String[] strings2a = list.toArray(new String[list.size()]);
String[] strings2b = list.toArray(new String[0]);

第一个 toArray() 方法(不带参数)返回一个 Object 数组,因为由于类型擦除,在运行时不再知道 list 的类型信息。

第二个 toArray() 方法需要一个请求类型的数组。如果此数组至少与集合一样大,则元素将存储在此数组中 (strings2a)。否则,将创建所需大小的新数组 (strings2b)。

从 Java 11 开始,我们还可以编写以下代码:

String[] strings = list.toArray(String[]::new);

此方法允许 Collection 类使用传递的数组构造函数引用创建必要大小的数组。

之所以我们可以用上面那种形式,是因为在 Java 11 中我们新增了下面这个 default 方法,用 generator 创建一个空数组,然后调用现有的 toArray() 方法:

default <T> T[] toArray(IntFunction<T[]> generator) {
  return toArray(generator.apply(0));
}

在 JDK 增强提案 JEP 中没有定义新的 toArray() 方法。

新的字符串方法

Java 11 为 String 类添加了一些新方法:isBlanklinesstripstripLeadingstripTrailingrepeat

新的文件方法

现在可以更容易从文件中读取和写入字符串。

我们可以使用 Files 类中新的 readStringwriteString 静态方法:

Path filePath = Files.writeString(Files.createTempFile(tempDir, "demo", ".txt"), "Sample text");
String fileContent = Files.readString(filePath);
assertThat(fileContent).isEqualTo("Sample text");

Path.of()

到目前为止,我们必须通过 Paths.get() 或 File.toPath() 创建 Path 对象。 Java 8 中接口默认方法的引入允许 JDK 开发人员将适当的工厂方法直接集成到 Path 接口中。

从 Java 11 开始,我们可以按如下方式创建路径对象,例如:

// Relative path foo/bar/baz
Path.of("foo/bar/baz");
Path.of("foo", "bar/baz");
Path.of("foo", "bar", "baz");

// Absolute path /foo/bar/baz
Path.of("/foo/bar/baz");
Path.of("/foo", "bar", "baz");
Path.of("/", "foo", "bar", "baz");

作为参数,我们可以指定整个路径或路径的一部分——任意组合,如示例中所示。

要定义绝对路径,在 Linux 和 macOS 上,第一部分必须以“/”开头,Windows 上以驱动器号开头,例如 “C:”。

Epsilon:一个无操作垃圾收集器 (No-Op Garbage Collector) (JEP 318)

使用 JDK 11,我们获得了一个新的垃圾收集器:Epsilon GC。

Epsilon GC 只处理内存分配,并没有实现任何内存回收机制。

不收集垃圾的垃圾收集器的目的是什么?

Epsilon GC 可以用于以下场景:

  • 性能测试:例如,在微型基准测试中,当我们比较不同的算法实现时,常规的垃圾收集器是一个障碍,因为它会影响执行时间,从而伪造测量结果。通过使用 Epsilon GC,我们可以排除此类影响。
  • 生命周期极短的应用程序,例如为 AWS Lambda 开发的应用程序,应尽快终止。如果应用程序在几毫秒后终止,那么垃圾回收周期将是浪费时间。
  • 消除延迟:如果开发人员对他们的应用程序的内存需求有很好的理解并且完全(或几乎完全)免除了对象分配,Epsilon GC 使他们能够实现无延迟的应用程序。

我们可以在 java 命令行中使用以下选项激活 Epsilon GC(类似于所有其他垃圾收集器):

-XX:+UseEpsilonGC

启动单文件源代码程序 (JEP 330)

对于只包含一个类的小型 Java 程序,我们不再需要用 javac 先编译我们的源代码了。

JEP 330 使得使用 java 命令编译和执行 .java 文件成为可能。我们甚至可以使 .java 文件成为可执行文件。

我们将通过一个简单的示例来展示。

创建一个名为 Hello.java 和以下内容的文件:

public class Hello {
  public static void main(String[] args) {
    if (args.length > 0) {
      System.out.printf("Hello %s!%n", args[0]);
    } else {
      System.out.println("Hello!");
    }
  }
}

在 Java 11 之前的版本,我们必须先用 javac 编译这个程序,然后用 java 运行它:

$ javac Hello.java
$ java Hello Bob

Hello Bob!

从 Java 11 开始,我们可以省略第一步:

$ java Hello.java Bob

Hello Bob!

源代码被编译到工作内存中并从那里执行。

在 Linux 和 macOS 上,我们可以更进一步,直接编写可执行的 Java 脚本。为此,我们必须在第一行插入一个"shebang"和源版本:

#!/usr/bin/java --source 11

public class Hello {
  public static void main(String[] args) {
    if (args.length > 0) {
      System.out.printf("Hello %s!%n", args[0]);
    } else {
      System.out.println("Hello!");
    }
  }
}

该文件不得具有 .java 扩展名。将其重命名为 Hello 并使其可执行:

mv Hello.java Hello
chmod +x Hello

现在我们可以直接执行

$ ./Hello Bob

Hello Bob!

很酷。对于较小的工具,这可能非常有用。

基于嵌套的访问控制 (JEP 181)

在 Java 11 之前使用内部类时,我们经常会遇到下面的问题:

public class Outer {

    private void outerPrivate() {

    }

    class Inner {

        public void innerPublic() {
            outerPrivate(); // access Outer's private method!
        }
    }
}

如果我们编译上面的源码,会生成2个类,Outer, Outer$Inner,即使是嵌套类也是一个典型的类,名字是唯一的。 JVM 访问规则将不允许不同类中的私有访问。

但是,Java 允许在嵌套成员中进行私有访问,因此 Java 编译器创建了一个桥接方法 access$000 以应用于 JVM 访问规则。这样整个代码看上去就像这样

public class Outer {
  private void outerPrivate() {

  }
  void access$000(){
    outerPrivate();
  }
}

class Inner {
  final Outer obj; 
  public void innerPublic() {
      obj.access$000(); // access Outer's private method!
  }
}

在使用反射时也会遇到类似的问题,但是 reflection API 会拒绝访问。 这是令人惊讶的,因为反射调用的行为应该与一般调用相同,即都可以访问嵌套的私有方法。

例如,如果我们尝试从 Inner 类反射地调用 outerPrivate():

public void innerPublicReflection(Outer ob) throws Exception {
    Method method = ob.getClass().getDeclaredMethod("outerPrivate");
    method.invoke(ob);
}

我们会得到一个报错:

java.lang.IllegalAccessException: 
class Outer$Inner cannot access a member of class Outer with modifiers "private"

Java 11 试图解决这些问题。它在 JVM 中引入了嵌套的概念和相关的访问规则。这简化了 Java 源代码编译器的工作。

为了实现这一点,类文件格式现在包含两个新属性:

  • 嵌套宿主包含一个属性 (NestMembers) 来标识其他静态已知的嵌套成员。
  • 每个其他嵌套成员都有一个属性 (NestHost) 来标识其嵌套宿主。

现在编译器不需要生成桥接方法。最后,基于嵌套的访问控制从 reflection API 中移除了令人惊讶的行为。因此,上面的方法 innerPublicReflection() 将不再报错。

分析工具

Java 飞行记录器 (JEP 328)

许多工具帮助我们在开发过程中分析和修复错误。但是,某些问题只发生在应用程序的运行时。分析它们通常很困难或不可能,因为我们通常无法重现此类错误。

Java Flight Recorder (JFR) 可以帮助我们在运行时记录 JVM 数据并将其保存在文件中以供后续分析。

Flight Recorder 作为 Oracle JDK 中的商业功能已经存在了好几年。通过 JEP 328,它成为 OpenJDK 的一部分,因此可以自由使用。

我们可以通过两种方式启动 Flight Recorder。首先,我们可以在应用程序启动时使用 java 命令行选项激活它:

-XX:StartFlightRecording=filename=<file name>

其次,我们可以使用 jcmd 在正在运行的 Java 应用程序中激活 Flight Recorder:

jcmd JFR.start filename=

我们可以指定许多选项;例如,我们可以使用“持续时间”来指定记录器应该运行多长时间。详细介绍所有选项将超出本文的范围。我们可以在 Oracle 的官方 Flight Recorder 文档 中找到它们。

低开销的堆分析 (JEP 331)

分析内存问题(例如,高垃圾收集器延迟或 OutOfMemoryErrors)的一个重要工具是用于分析位于堆上的对象的堆转储。 市场为此提供了许多工具。 到目前为止,这些工具都没有告诉我们堆上的对象是在代码中的哪个位置创建的。

借助 JEP 331,Java 虚拟机工具接口 (Java Virtual Machine Tool Interface - JVMTI)(即这些工具获取有关正在运行的应用程序的数据的接口)通过收集所有对象分配的堆栈跟踪的可能性得到了扩展。 堆分析工具可以显示这些附加信息,从而使我们开发人员更容易分析问题。

实验性的新功能

ZGC:可扩展的低延迟垃圾收集器(实验性)(JEP 333)

“Z 垃圾收集器”(简称 ZGC)是 Oracle 开发的一种替代垃圾收集器,可将完整 GC(即跨所有堆区域的完整收集)的暂停时间减少到最大 10 毫秒,而不会将整体吞吐量降低, 与 G1 垃圾收集器相比不超过 15%。

目前,ZGC 仅适用于 Linux。 我们可以使用以下 JVM 标志启用它:

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

ZGC 将在 Java 15 中达到生产状态。

弃用和删除的功能

本节列出了标记为“已弃用”或从 JDK 中删除的所有功能。

删除 Java EE 和 CORBA 模块 (JEP 320)

JEP 320 从 JDK 中删除了以下模块:

  • java.xml.ws (JAX-WS)
  • java.xml.bind (JAXB)
  • java.activation (JAF)
  • java.xml.ws.annotation(通用注解)
  • java.corba (CORBA)
  • java.transaction (JTA)
  • java.se.ee(前面提到的六个模块的聚合器模块)
  • jdk.xml.ws(JAX-WS 工具)
  • jdk.xml.bind(JAXB 工具)

列出的技术最初是为 Java EE 平台开发的,并在 Java 6 发布时集成到标准版“Java SE”中。它们在 Java 9 中被标记为“已弃用”,最终在 Java 11 中被删除。

如果我们在升级到 Java 11 后错过了这些库,我们可以将它们拉回到我们的项目中,例如,通过 Maven 依赖项。

弃用 Nashorn JavaScript 引擎 (JEP 335)

JDK 8 中引入的 JavaScript 引擎“Rhino”在 Java 11 中与 JEP 335 一起被标记为“弃用准备删除”,并将在后续某个版本中完全删除。

原因是 ECMAScript(JavaScript 后面的标准)和 node.js 引擎的快速发展,这使得 Rhino 的进一步开发成本太高。

弃用 Pack200 工具和 API (JEP 336)

Pack200 是 Java 5 中引入的一种特殊压缩方法,可实现比标准方法更高的压缩率,尤其是对于 .class 和 .jar 文件。 Pack200 的开发是为了在 2000 年代初期尽可能多地节省带宽。

但是,算法复杂,进一步的开发成本与百兆互联网线路时代的收益已经不成正比。

因此,该工具已在 JEP 336 中标记为“已弃用”,应在后续的某个版本中删除。

从 Oracle JDK 中移除 JavaFX

从 Java 11 开始,JavaFX(以及相关的 javapackager 工具)不再随 JDK 一起提供。相反,我们可以从 JavaFX 主页将其作为单独的 SDK 下载。

Java 11 中一些其他的改动 (作为一个 Java 开发可能不需要了解)

Unicode 10 (JEP 327)

借助 JEP 327,Java 11 已扩展为包括对 Unicode 10.0 的支持。

这意味着什么?

特别是 String 和 Character 类必须通过新的代码块和字符进行扩展。例如,这与 String.toUpperCase()toLowerCase() 以及 Character.getName()Character.UnicodeBlock.of() 相关。

这是一个简短的代码示例(Unicode 代码点 0x1F929 代表表情符号🤩):

System.out.println("name  = " + Character.getName(0x1F929));
System.out.println("block = " + Character.UnicodeBlock.of(0x1F929));

在 Java 11 之前,代码会打印以下内容:

name  = null
block = null

另一方面,Java 11 知道新的表情符号

name  = GRINNING FACE WITH STAR EYES
block = SUPPLEMENTAL_SYMBOLS_AND_PICTOGRAPHS

改进 Aarch64 内在函数 (JEP 315)

JEP 315 改进了现有的并为 AArch64 平台(即 64 位 ARM CPU)添加了新的所谓“Intrinsics”。

Intrinsics 用于执行特定于体系结构的汇编代码而不是 Java 代码,这显着提高了特定 JDK 类库方法的性能。

此 JEP 为三角函数和对数函数添加了内在函数,并优化了 String.compareTo()String.indexOf() 等方法的现有内在函数。

传输层安全 (TLS) 1.3 (JEP 332)

到目前为止,JDK 支持以下安全协议:

安全套接层 (SSL) 版本 3.0
传输层安全 (TLS) 版本 1.0、1.1 和 1.2
数据报传输层安全 (DTLS) 版本 1.0 和 1.2
JEP 332 扩展了这个列表以包括现代安全标准 TLS 1.3。

ChaCha20 和 Poly1305 加密算法 (JEP 329)

JEP 329 为 JDK 添加了两种加密算法——ChaCha20 和 Poly1305。例如,它们被上一节中提到的安全协议使用。

与 Curve25519 和 Curve448 的关键协议 (JEP 324)

在 JEP 324 中,JDK 的密钥交换协议通过椭圆曲线“Curve25519”和“Curve448”进行了扩展。两条曲线都支持使用对称密钥的高速加密和解密。

动态类文件常量 (JEP 309)

.class 文件格式被扩展为包含 CONSTANT_Dynamic 常量,为“语言设计者和编译器实现者提供更广泛的表达性和性能选项”。如果我们计划开发一种语言或编译器,我们可以在 JEP 309 中找到更多详细信息。

Java 11 中所有更改的完整列表

本文介绍了 JDK 增强提案中定义的 Java 11 的所有功能,以及与任何 JEP 无关的 JDK 类库的增强。

有关更改的完整列表,请参阅 官方 Java 11 Release Notes

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值