Java 11简介

18-09-26 banq
    

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

从Java 11开始var也可以用作lambda参数,从而能为这些参数添加注释:

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

提示:在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());
<p>

可以异步执行相同的请求。调用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);
<p>

我们可以省略.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
<p>

最后一个示例演示如何通过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
<p>

集合

诸如集合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
<p>

因为list已经是不可改变的,就没有实际需要创建这种列表实例的一个副本,因此list和copy是相同的实例。但是,如果你复制一个可变列表,copy确实是一个新实例,所以它保证在改变原始列表时没有副作用:

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

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

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

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

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

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

Stream.ofNullable(null)
    .count()   // 0
<p>

方法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]
<p>

Optionals

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

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

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
<p>

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);
}
<p>

Java 11 Tutorial