本教程涵盖了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);
}