Java 11简介

本教程涵盖了Java 9到11中最重要的语言和API特性。

局部变量类型推断
Java 10引入了一个新的语言关键字var,它可以在声明局部变量时替换类型信息(本地意味着方法体内的变量声明)。

在Java 10之前,你将如下声明变量:

String text = "Hello Java 9";

现在,你可以替换String使用var。编译器从变量的赋值中推断出正确的类型。在这种情况下text是类型String:

var text = "Hello Java 10";

声明的变量var仍然是静态类型。你无法将不兼容的类型重新分配给此类变量。下面代码段无法编译:

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;

提示:在Intellij IDEA中,你可以将鼠标悬停在变量上,同时按CMD/CTRL显示变量的感应类型(按键盘爱好者按下CTRL + J)。

HTTP客户端
Java 9引入了一个HttpClient用于处理HTTP请求的新的API。从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);

我们可以省略.GET()调用,因为它是默认的请求方法。

下一个示例通过发送数据到指定的URL进行POST。类似BodyHandlers你可以用BodyPublishers来定义要作为请求主体发送的数据类型,如字符串,字节数组,文件或输入流:


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

集合
诸如集合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和copy是相同的实例。但是,如果你复制一个可变列表,copy确实是一个新实例,所以它保证在改变原始列表时没有副作用:


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

创建不可变map时,你不必自己创建map条目,而是将键和值作为参数传递:


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

Java 11中的不可变集合仍然使用旧Collection API中的相同接口。但是,如果尝试通过添加或删除元素来修改不可变集合,则抛出java.lang.UnsupportedOperationException。

幸运的是,如果你尝试改变不可变集合,Intellij IDEA会通过检查发出警告。


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]


Optionals
Optionals还会有一些非常方便的新方法,例如,你现在可以简单地将Optionals转换为流,或者为空Optionals提供另一个可选的后备:


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

String字符串
最基本的类之一String添加一些辅助方法来修剪或检查空格以及流式传输字符串的行:


" ".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);
}

Java 11 Tutorial