之前工作使用的 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);
}