Java 11 中的新特性

1,508 阅读4分钟

之前工作使用的 Java 环境是 JDK 1.8 ,新公司使用 Java 11 作为新的开发版本,很多 Java 11 的特性没有了解,因此这篇文章就来总结一下 Java 11 中哪些新功能值的一用。

局部变量类型推断

Java 10 引入了一个新的语言关键字 var,它可以在声明局部变量时选择性地替换类型信息。

在 Java 10 之前我们都是这样声明变量的

String text = "Hello Java 8";

现在你可以用 var 替换 String。编译器从变量的赋值中推断出正确的类型。在本例中,text的类型是 String 。

var text = "Hello Java 10";

用 var 声明的变量仍然是静态类型的。您不能将不兼容 Incompatible 的类型重新分配给这些变量。此代码片段无法编译

var text = "Hello Java 11";
text = 23;  // 错误  Incompatible types

您还可以结合使用 final 和 var 来禁止用另一个值对变量重新赋值

final var text = "Banana";
text = "Joe";   // Cannot assign a value to final variable 'text'

同样,当编译器不能推断出变量的正确类型时,也不允许使用var。下面的所有代码示例都会导致编译器错误

// Cannot infer type:
var a;
var nothing = null;
var lambda = () -> System.out.println("Pity!");
var method = this::someMethod;

局部变量类型推断在泛型方面非常出色。在下一个例子中,current 有一个相当详细的类型Map<String, List<Integer>>,可以简化为一个 var 关键字,节省您输入大量样板代码

var myList = new ArrayList<Map<String, List<Integer>>>();

for (var current : myList) {
    // current is infered to type: Map<String, List<Integer>>
    System.out.println(current);
}

从 Java 11开始,var关键字也允许用于 lambda 参数,这使您能够向这些参数添加注释

Predicate<String> predicate = (@Nullable var a) -> true;

HttpClient

Java 9 引入了一个新的酝酿中的 HttpClient API 来处理 HTTP 请求。从 Java 11开始,这个API 现在是最终版本,可以在标准库包 java.net 中使用。让我们来实验一下这个 API 能做些什么。

新的 HttpClient 既可以同步使用,也可以异步使用。同步请求阻塞当前线程,直到响应可用为止。BodyHandlers 定义预期的响应体类型(例如字符串、字节数组或文件)。

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://winterbe.com"))
    .GET()
    .build();
var client = HttpClient.newHttpClient();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());

同样的请求可以异步执行。调用 sendAsync 不会阻塞当前线程,而是返回一个CompletableFuture 来构建异步操作管道。

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://winterbe.com"))
    .build();
var client = HttpClient.newHttpClient();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
    .thenApply(HttpResponse::body)
    .thenAccept(System.out::println);

下一个例子是通过 POST 将数据发送到给定的 URL。与 BodyHandlers 类似,您使用bodypublisher 定义希望作为请求体发送的数据类型,如字符串、字节数组、文件或输入流。

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://postman-echo.com/post"))
    .header("Content-Type", "text/plain")
    .POST(HttpRequest.BodyPublishers.ofString("Hi there!"))
    .build();
var client = HttpClient.newHttpClient();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());      // 200

 最后一个示例演示如何通过 BASIC-AUTH 执行授权

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://postman-echo.com/basic-auth"))
    .build();
var client = HttpClient.newBuilder()
    .authenticator(new Authenticator() {
        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication("postman", "password".toCharArray());
        }
    })
    .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());      // 200

集合 Collections

像 List、Set 和 Map 这样的集合已经用新的方法进行了扩展。List.of 根据给定的参数创建了一个新的不可变列表。List.copyOf 创建列表的不可变副本。

var list = List.of("A", "B", "C");
var copy = List.copyOf(list);
System.out.println(list == copy);   // true

因为 list 已经是不可变的,所以实际上没有必要创建 list 实例的副本,因此 list 和 copy 是同一个实例。然而,如果复制一个可变列表,复制的实例与原实例不同。

var list = new ArrayList<String>();
var copy = List.copyOf(list);
System.out.println(list == copy);   // false

在创建不可变映射时,您不必自己创建映射项,而是将键和值作为交替参数传递

var map = Map.of("A", 1, "B", 2);
System.out.println(map);    // {B=2, A=1}

数据流 Streams

流是在 Java 8 中引入的,现在增加了三个新方法。Stream.ofNullable 从单个元素构造流:

Stream.ofNullable(null)
    .count()   // 0

dropWhile 和 takeWhile 方法都接受一个谓词来决定从流中放弃或选用哪些元素

Stream.of(1, 2, 3, 2, 1)
    .dropWhile(n -> n < 3)
    .collect(Collectors.toList());  // [3, 2, 1]

Stream.of(1, 2, 3, 2, 1)
    .takeWhile(n -> n < 3)
    .collect(Collectors.toList());  // [1, 2]

如果你对 Stream 不熟悉,可以查看我写的另一篇文章 Java 流的使用

Optionals

Optionals  增加了非常方便的新方法,例如,你现在可以简单地将 Optionals 转换为流,或者提供另一个 Optionals  作为空 Optionals 的回调

Optional.of("foo").orElseThrow();     // foo
Optional.of("foo").stream().count();  // 1
Optional.ofNullable(null)
    .or(() -> Optional.of("fallback"))
    .get();       

Strings

最基本的类之一 String 增加了一些辅助方法,用于修剪 trimming 或检查 checking 空白,以及将字符串转化为流。

" ".isBlank();                // true
" Foo Bar ".strip();          // "Foo Bar"
" Foo Bar ".stripTrailing();  // " Foo Bar"
" Foo Bar ".stripLeading();   // "Foo Bar "
"Java".repeat(3);             // "JavaJavaJava"
"A\nB\nC".lines().count();    // 3

InputStreams

InputStream 增加了一个超级有用的方法来将数据传输到 OutputStream,这是一个在处理原始数据流时非常常见的用例。

var classLoader = ClassLoader.getSystemClassLoader();
var inputStream = classLoader.getResourceAsStream("myFile.txt");
var tempFile = File.createTempFile("myFileCopy", "txt");
try (var outputStream = new FileOutputStream(tempFile)) {
    inputStream.transferTo(outputStream);
}

参考资料