一、背景
本文旨在介绍Java11相对Java8的新特性,包括Java9与Java10引入的特性。主要是新的语法特性,模块化开发,以及其他方面的一些新特性。
本文涉及到的代码位于:https://github.com/zhaochuninhefei/study-czhao/tree/master/jdk11-test
或:https://gitee.com/XiaTangShaoBing/study/tree/master/jdk11-test
二、新的语法特性
Java11相对Java8,在语法上的新特性并不多。主要有:
- 本地变量类型推断
- HttpClient
- Collection增强
- Stream增强
- Optional增强
- String增强
- InputStream增强
2.1 本地变量类型推断
Java10以后可以用var定义一个局部变量,不用显式写出它的类型。但要注意,被var定义的变量仍然是静态类型,编译器会试图去推断其类型。
String strBeforeJava10 = "strBeforeJava10";
var strFromJava10 = "strFromJava10";
System.out.println(strBeforeJava10);
System.out.println(strFromJava10);
因此,要注意:
- 不兼容的类型是不能重新赋值的!
// 例如下面的语句编译会失败,"InCompatible types."
strFromJava10 = 10;
- 只要编译器无法推断出变量类型,就会编译错误!
// 例如下面这些都无法通过编译:
var testVarWithoutInitial;
var testNull = null;
var testLamda = () -> System.out.println("test");
var testMethodByLamda = () -> giveMeString();
var testMethod2 = this::giveMeString;
而推荐使用类型推断的场景有:
- 简化泛型声明
// 如下所示,Map <String,List <Integer >>类型,可以被简化为单个var关键字
var testList = new ArrayList<Map<String, List<Integer>>>();
for (var curEle : testList) {
// curEle能够被推断出类型是 Map<String, List<Integer>>
if (curEle != null) {
curEle.put("test", new ArrayList<>());
}
}
- lambda参数
// 从Java 11开始,lambda参数也允许使用var关键字:
Predicate<String> predNotNull = (var a) -> a != null && a.trim().length() > 0;
String strAfterFilter = Arrays.stream((new String[]{"a", "", null, "x"}))
.filter(predNotNull)
.collect(Collectors.joining(","));
System.out.println(strAfterFilter);
完整的演示代码:
package jdk11;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* 本地变量类型推断
*
* @author zhaochun
*/
public class TestCase01TypeInference {
public static void main(String[] args) {
TestCase01TypeInference me = new TestCase01TypeInference();
me.testVar();
}
private void testVar() {
// Java10以后可以用var定义一个局部变量,不用显式写出它的类型。
String strBeforeJava10 = "strBeforeJava10";
var strFromJava10 = "strFromJava10";
System.out.println(strBeforeJava10);
System.out.println(strFromJava10);
// 但要注意,被var定义的变量仍然是静态类型,编译器会试图去推断其类型。
// 因此不兼容的类型是不能重新赋值的。
// 例如下面的语句编译会失败,"InCompatible types."
// strFromJava10 = 10;
// 只要编译器无法推断出变量类型,就会编译错误!
// 例如下面这些都无法通过编译:
// var testVarWithoutInitial;
// var testNull = null;
// var testLamda = () -> System.out.println("test");
// var testMethodByLamda = () -> giveMeString();
// var testMethod2 = this::giveMeString;
// 局部变量类型推断可以用于简化泛型声明。如下所示,Map <String,List <Integer >>类型,可以被简化为单个var关键字,从而避免大量样板代码:
var testList = new ArrayList<Map<String, List<Integer>>>();
for (var curEle : testList) {
// curEle能够被推断出类型是 Map<String, List<Integer>>
if (curEle != null) {
curEle.put("test", new ArrayList<>());
}
}
// 从Java 11开始,lambda参数也允许使用var关键字:
Predicate<String> predNotNull = (var a) -> a != null && a.trim().length() > 0;
String strAfterFilter = Arrays.stream((new String[]{"a", "", null, "x"}))
.filter(predNotNull)
.collect(Collectors.joining(","));
System.out.println(strAfterFilter);
}
private String giveMeString() {
return "a string.";
}
}
2.2 HttpClient
Java 9开始引入HttpClient API来处理HTTP请求。 从Java 11开始,这个API正式进入标准库包。参考网址:http://openjdk.java.net/groups/net/httpclient/intro.html
HttpClient具有以下特性:
- 同时支持 HTTP1.1 和 HTTP2 协议,并支持 websocket
- 同时支持同步和异步编程模型
- 将请求和响应主体作为响应式流(reactive-streams)处理,并使用构建器模式
HttpClient
要发送http请求,首先要使用其构建器创建一个HttpClient。这个构建器能够配置每个客户端的状态:
- 首选协议版本 ( HTTP/1.1 或 HTTP/2 )
- 是否跟随重定向
- 代理
- 身份验证
一旦构建完成,就可以使用HttpClient发送多个请求。
HttpRequest
HttpRequest是由它的构建器创建的。请求的构建器可用于设置:
- 请求URI
- 请求Method ( GET, PUT, POST )
- 请求主体(如果有)
- 超时时间
- 请求头
HttpRequest构建之后是不可变的,但可以发送多次。
Synchronous or Asynchronous
请求既可以同步发送,也可以异步发送。当然同步的API会导致线程阻塞直到HttpResponse可用。异步API立即返回一个CompletableFuture,当HttpResponse可用时,它将获取HttpResponse并执行后续处理。
CompletableFuture是Java 8添加的新特性,用于可组合的异步编程。
Data as reactive-streams
请求和响应的主体作为响应式流(具有非阻塞背压的异步数据流)供外部使用。HttpClient实际上是请求正文的订阅者和响应正文字节的发布者。BodyHandler接口允许在接收实际响应体之前检查响应代码和报头,并负责创建响应BodySubscriber。
HttpRequest和HttpResponse类型提供了许多便利的工厂方法,用于创建请求发布者和响应订阅者,以处理常见的主体类型,如文件、字符串和字节。这些便利的实现要么累积数据,直到可以创建更高级别的Java类型(如String),要么就文件流传输数据。BodySubscriber和BodyPublisher接口可以实现为自定义反应流处理数据。
HttpRequest和HttpResponse还提供了转换器,用于将 java.util.concurrent.Flow 的 Publisher/Subscriber 类型转换为 HTTP Client的 BodyPublisher/BodySubscriber 类型。
HTTP/2
Java HTTP Client支持 HTTP/1.1 和 HTTP/2。默认情况下,客户端将使用 HTTP/2 发送请求。发送到尚不支持 HTTP/2 的服务器的请求将自动降级为 HTTP/1.1。以下是HTTP/2带来的主要改进:
- 标头压缩。 HTTP/2 使用 HPACK 压缩,从而减少了开销。
- 与服务器的单一连接减少了建立多个TCP连接所需的往返次数。
- 多路复用。 在同一连接上,同时允许多个请求。
- 服务器推送。 可以将其他将来需要的资源发送给客户端。
- 二进制格式。 更紧凑。
由于HTTP/2是默认的首选协议,并且在需要的地方无缝地实现回退到HTTP/1.1,那么当HTTP/2被更广泛地部署时,Java HTTP客户端就无需修正它的应用代码。
API文档
https://docs.oracle.com/en/ja...
演示代码
代码中请求的网址中,localhost:30001
的相关uri来自工程https://github.com/zhaochuninhefei/study-czhao/tree/master/jdk11-test
。
package jdk11;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.WebSocket;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
/**
* HttpClient
*
* @author zhaochun
*/
public class TestCase02HttpClient {
public static void main(String[] args) throws Exception {
TestCase02HttpClient me = new TestCase02HttpClient();
me.testHttpClientGetSync();
me.testHttpClientGetAsync();
me.testHttpClientPost();
// 同一个HttpClient先登录网站获取token,再请求受限制资源,从而爬取需要认证的资源
me.testLogin();
// HttpClient支持websocket
me.testWebsocket();
}
private void testHttpClientGetSync() {
var url = "https://openjdk.java.net/";
var request = HttpRequest.newBuilder()
.uri(URI.create(url))
.GET()
.build();
var client = HttpClient.newHttpClient();
try {
System.out.println(String.format("send begin at %s", LocalDateTime.now()));
// 同步请求
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(String.format("send end at %s", LocalDateTime.now()));
System.out.println(String.format("receive response : %s", response.body().substring(0, 10)));
} catch (Exception e) {
e.printStackTrace();
}
}
private void testHttpClientGetAsync() {
var url = "https://openjdk.java.net/";
var request = HttpRequest.newBuilder()
.uri(URI.create(url))
.GET()
.build();
var client = HttpClient.newHttpClient();
try {
System.out.println(String.format("sendAsync begin at %s", LocalDateTime.now()));
// 异步请求
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(stringHttpResponse -> {
System.out.println(String.format("receive response at %s", LocalDateTime.now()));
return stringHttpResponse.body();
})
.thenAccept(s -> System.out.println(String.format("receive response : %s at %s", s.substring(0, 10), LocalDateTime.now())));
System.out.println(String.format("sendAsync end at %s", LocalDateTime.now()));
// 为了防止异步请求尚未返回主线程就结束(jvm会退出),这里让主线程sleep 10秒
System.out.println("Main Thread sleep 10 seconds start...");
Thread.sleep(10000);
System.out.println("Main Thread sleep 10 seconds stop...");
} catch (Exception e) {
e.printStackTrace();
}
}
private void testHttpClientPost() {
var url = "http://localhost:30001/jdk11/test/helloByPost";
var request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "text/plain")
.POST(HttpRequest.BodyPublishers.ofString("zhangsan"))
.build();
var client = HttpClient.newHttpClient();
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
private void testLogin() throws Exception {
var client = HttpClient.newHttpClient();
// 某测试环境用户登录URL
var urlLogin = "http://x.x.x.x:xxxx/xxx/login";
var requestObj = new HashMap<String, Object>();
requestObj.put("username", "xxxxxx");
requestObj.put("password", "xxxxxxxxxxxxxxxx");
var objectMapper = new ObjectMapper();
var requestBodyJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(requestObj);
var requestLogin = HttpRequest.newBuilder()
.uri(URI.create(urlLogin))
.header("Content-Type", "application/json;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofString(requestBodyJson))
.build();
HttpResponse<String> responseLogin = client.send(requestLogin, HttpResponse.BodyHandlers.ofString());
// 这里的登录网站使用token,而没有使用session,因此我们需要从返回的报文主体中查找token信息;
// 如果是使用session的网站,这里需要从响应的headers中查找"set-cookie"从而获取session id,并在后续请求中,将sid设置到header的Cookie中。
// 如: responseLogin.headers().map().get("set-cookie")获取cookies,再从中查找sid。
var loginResponse = responseLogin.body();
var mpLoginResponse = objectMapper.readValue(loginResponse, Map.class);
var dataLogin = (Map<String, Object>) mpLoginResponse.get("data");
var token = dataLogin.get("token").toString();
// 测试环境获取某资源的URL
var urlGetResource = "http://xxxx:xxxx/xxx/resource";
var requestRes = HttpRequest.newBuilder()
.uri(URI.create(urlGetResource))
.header("Content-Type", "application/json;charset=UTF-8")
// 注意,token并非一定设置到header的Authorization中,这取决于网站验证的方式,也有可能token也放到cookie里。
// 但对于使用session的网站,sid都是设置在cookie里的。如: .header("Cookie", "JSESSIONID=" + sid)
.header("Authorization", token)
.GET()
.build();
HttpResponse<String> responseResource = client.send(requestRes, HttpResponse.BodyHandlers.ofString());
var response = responseResource.body();
System.out.println(response);
}
private void testWebsocket() {
var wsUrl = "ws://localhost:30001/ws/test";
var httpClient = HttpClient.newHttpClient();
WebSocket websocketClient = httpClient.newWebSocketBuilder()
.buildAsync(URI.create(wsUrl), new WebSocket.Listener() {
@Override
public void onOpen(WebSocket webSocket) {
System.out.println("onOpen : webSocket opened.");
webSocket.request(1);
}
@Override
public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
System.out.println("onText");
webSocket.request(1);
return CompletableFuture.completedFuture(data)
.thenAccept(System.out::println);
}
@Override
public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) {
System.out.println("ws closed with status(" + statusCode + "). cause:" + reason);
webSocket.sendClose(statusCode, reason);
return null;
}
@Override
public void onError(WebSocket webSocket, Throwable error) {
System.out.println("error: " + error.getLocalizedMessage());
webSocket.abort();
}
}).join();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
// last参数用于指示websocketClient,本次发送的数据是否是完整消息的最后部分。
// 如果是false,则websocketClient不会把消息发送给websocket后台的listener,只会把数据缓存起来;
// 当传入true时,会将之前缓存的数据和这次的数据拼接起来一起发送给websocket后台的listener。
websocketClient.sendText("test1", false);
websocketClient.sendText("test2", true);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
websocketClient.sendText("org_all_request", true);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
websocketClient.sendText("employee_all_request", true);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
websocketClient.sendClose(WebSocket.NORMAL_CLOSURE, "Happy ending.");
}
}
2.3 Collection增强
List,Set,Map有了新的增强方法:of
与copyOf
。
List的of与copyOf
List.of根据传入的参数列表创建一个新的不可变List集合;List.copyOf根据传入的list对象创建一个不可变副本。
var listImmutable = List.of("a", "b", "c");
var listImmutableCopy = List.copyOf(listImmutable);
由于拷贝的集合本身就是一个不可变对象,因此拷贝实际上并没有创建新的对象,直接使用了原来的不可变对象。
// 结果为true
System.out.println(listImmutable == listImmutableCopy);
// 不可变对象不能进行修改
try {
listImmutable.add("d");
} catch (Throwable t) {
System.out.println("listImmutable can not be modified!");
}
try {
listImmutableCopy.add("d");
} catch (Throwable t) {
System.out.println("listImmutableCopy can not be modified!");
}
如果想快速新建一个可变的集合对象,可以直接使用之前的不可变集合作为构造参数,创建一个新的可变集合。
var listVariable = new ArrayList<>(listImmutable);
var listVariableCopy = List.copyOf(listVariable);
新创建的可变集合当然是一个新的对象,从这个新对象拷贝出来的不可变副本也是一个新的对象,并不是之前的不可变集合。
System.out.println(listVariable == listImmutable); // false
System.out.println(listVariable == listVariableCopy); // false
System.out.println(listImmutable == listVariableCopy); // false
// 新的可变集合当然是可以修改的
try {
listVariable.add("d");
} catch (Throwable t) {
System.out.println("listVariable can not be modified!");
}
// 可变集合拷贝出来的副本依然是不可变的
try {
listVariableCopy.add("d");
} catch (Throwable t) {
System.out.println("listVariableCopy can not be modified!");
}
Set的of和copyOf
Set的of和copyOf与List类似。
var set = Set.of("a", "c", "r", "e");
var setCopy = Set.copyOf(set);
System.out.println(set == setCopy);
但要注意,用of创建不可变Set时,要确保元素不重复,否则运行时会抛出异常: "java.lang.IllegalArgumentException: duplicate element"
try {
var setErr = Set.of("a", "b", "a");
} catch (Throwable t) {
t.printStackTrace();
}
当然创建可变set后添加重复元素不会抛出异常,但会被去重
var setNew = new HashSet<>(set);
setNew.add("c");
System.out.println(setNew.toString());
Map的of和copyOf
Map的of和copyOf与list,set类似,注意of方法的参数列表是依次传入key和value:
var map = Map.of("a", 1, "b", 2);
var mapCopy = Map.copyOf(map);
System.out.println(map == mapCopy);
当然也要注意创建不可变Map时,key不能重复
try {
var mapErr = Map.of("a", 1, "b", 2, "a", 3);
} catch (Throwable t) {
t.printStackTrace();
}
完整的示例代码
package jdk11;
import java.util.*;
/**
* Collection增强
*
* @author zhaochun
*/
public class TestCase03Collection {
public static void main(String[] args) {
TestCase03Collection me = new TestCase03Collection();
me.test01_of_copyOf();
}
private void test01_of_copyOf() {
// List,Set,Map有了新的增强方法。
// List.of根据传入的参数列表创建一个新的不可变List集合;
// List.copyOf根据传入的list对象创建一个不可变副本。
var listImmutable = List.of("a", "b", "c");
var listImmutableCopy = List.copyOf(listImmutable);
// 由于拷贝的集合本身就是一个不可变对象,因此拷贝实际上并没有创建新的对象,直接使用了原来的不可变对象。
System.out.println(listImmutable == listImmutableCopy);
// 不可变对象不能进行修改
try {
listImmutable.add("d");
} catch (Throwable t) {
System.out.println("listImmutable can not be modified!");
}
try {
listImmutableCopy.add("d");
} catch (Throwable t) {
System.out.println("listImmutableCopy can not be modified!");
}
// 如果想快速新建一个可变的集合对象,可以直接使用之前的不可变集合作为构造参数,创建一个新的可变集合。
var listVariable = new ArrayList<>(listImmutable);
var listVariableCopy = List.copyOf(listVariable);
// 新创建的可变集合当然是一个新的对象,从这个新对象拷贝出来的不可变副本也是一个新的对象,并不是之前的不可变集合。
System.out.println(listVariable == listImmutable);
System.out.println(listVariable == listVariableCopy);
System.out.println(listImmutable == listVariableCopy);
// 新的可变集合当然是可以修改的
try {
listVariable.add("d");
} catch (Throwable t) {
System.out.println("listVariable can not be modified!");
}
// 可变集合拷贝出来的副本依然是不可变的
try {
listVariableCopy.add("d");
} catch (Throwable t) {
System.out.println("listVariableCopy can not be modified!");
}
// Set的of和copyOf与List类似。
var set = Set.of("a", "c", "r", "e");
var setCopy = Set.copyOf(set);
System.out.println(set == setCopy);
// 但要注意,用of创建不可变Set时,要确保元素不重复,否则运行时会抛出异常: "java.lang.IllegalArgumentException: duplicate element"
try {
var setErr = Set.of("a", "b", "a");
} catch (Throwable t) {
t.printStackTrace();
}
// 当然创建可变set后添加重复元素不会抛出异常,但会被去重
var setNew = new HashSet<>(set);
setNew.add("c");
System.out.println(setNew.toString());
// Map的of和copyOf与list,set类似
var map = Map.of("a", 1, "b", 2);
var mapCopy = Map.copyOf(map);
System.out.println(map == mapCopy);
// 当然也要注意创建不可变Map时,key不能重复
try {
var mapErr = Map.of("a", 1, "b", 2, "a", 3);
} catch (Throwable t) {
t.printStackTrace();
}
}
}
2.4 Stream增强
Java8开始引入stream,Java11提供了一些扩展:
- 单个元素直接构造为Stream对象
- dropWhile与takeWhile
- 重载iterate方法用于限制无限流范围
单个元素直接构造为Stream对象
注意null与""的区别:
long size1 = Stream.ofNullable(null).count();
System.out.println(size1); // 0
long size2 = Stream.ofNullable("").count();
System.out.println(size2); // 1
dropWhile与takeWhile
dropWhile,对于有序的stream,从头开始去掉满足条件的元素,一旦遇到不满足元素的就结束
List lst1 = Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1)
.dropWhile(e -> e < 3)
.collect(Collectors.toList());
System.out.println(lst1); // [3, 4, 5, 4, 3, 2, 1]
takeWhile,对于有序的stream,从头开始保留满足条件的元素,一旦遇到不满足的元素就结束
List lst2 = Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1)
.takeWhile(e -> e < 3)
.collect(Collectors.toList());
System.out.println(lst2); // [1, 2]
即使把剩下的元素都收集到了无序的set中,但在此之前,stream对象是有序的,因此结果包含了原来stream中最后的[a2]和[a1]:
Set set1 = Stream.of("a1", "a2", "a3", "a4", "a5", "a4", "a3", "a2", "a1")
.dropWhile(e -> "a3".compareTo(e) > 0)
.collect(Collectors.toSet());
System.out.println(set1); // [a1, a2, a3, a4, a5]
如果先创建一个无序不重复的set集合,set无序更准确的说法是不保证顺序不变,事实上是有顺序的。
因此这里会发现,dropWhile还是按set当前的元素顺序判定的,一旦不满足条件就结束。
Set<String> set = new HashSet<>();
for (int i = 1; i <= 100 ; i++) {
set.add("test" + i);
}
System.out.println(set);
Set setNew = set.stream()
.dropWhile(s -> "test60".compareTo(s) > 0)
.collect(Collectors.toSet());
System.out.println(setNew);
重载iterate方法用于限制无限流范围
java8里可以创建一个无限流,比如下面这个数列,起始值是1,后面每一项都在前一项的基础上 * 2 + 1,通过limit限制这个流的长度:
Stream<Integer> streamInJava8 = Stream.iterate(1, t -> 2 * t + 1);
// 打印出该数列的前十个: 1,3,7,15,31,63,127,255,511,1023
System.out.println(streamInJava8.limit(10).map(Object::toString).collect(Collectors.joining(",")));
从Java9开始,iterate方法可以添加一个判定器,例如,限制数的大小不超过1000
Stream<Integer> streamFromJava9 = Stream.iterate(1, t -> t < 1000, t -> 2 * t + 1);
// 这里打印的结果是 1,3,7,15,31,63,127,255,511
System.out.println(streamFromJava9.map(Objects::toString).collect(Collectors.joining(",")));
完整示例代码
package jdk11;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Stream增强
*
* @author zhaochun
*/
public class TestCase04Stream {
public static void main(String[] args) {
TestCase04Stream me = new TestCase04Stream();
me.test01_ofNullable();
me.test02_dropWhile_takeWhile();
me.test03_iterate();
}
private void test01_ofNullable() {
// 单个参数的Stream构造方法
long size1 = Stream.ofNullable(null).count();
System.out.println(size1);
long size2 = Stream.ofNullable("").count();
System.out.println(size2);
}
private void test02_dropWhile_takeWhile() {
// dropWhile 对于有序的stream,从头开始去掉满足条件的元素,一旦遇到不满足元素的就结束
List lst1 = Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1)
.dropWhile(e -> e < 3)
.collect(Collectors.toList());
System.out.println(lst1);
// takeWhile 对于有序的stream,从头开始保留满足条件的元素,一旦遇到不满足的元素就结束
List lst2 = Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1)
.takeWhile(e -> e < 3)
.collect(Collectors.toList());
System.out.println(lst2);
// 虽然这里最后把剩下的元素都收集到了无序的set中,但在此之前,stream对象是有序的,因此结果包含了原来stream中最后的[a2]和[a1]
Set set1 = Stream.of("a1", "a2", "a3", "a4", "a5", "a4", "a3", "a2", "a1")
.dropWhile(e -> "a3".compareTo(e) > 0)
.collect(Collectors.toSet());
System.out.println(set1);
// 这里先创建一个无序不重复的set集合,set无序更准确的说法是不保证顺序不变,事实上是有顺序的。
// 因此这里会发现,dropWhile还是按set当前的元素顺序判定的,一旦不满足条件就结束。
Set<String> set = new HashSet<>();
for (int i = 1; i <= 100 ; i++) {
set.add("test" + i);
}
System.out.println(set);
Set setNew = set.stream()
.dropWhile(s -> "test60".compareTo(s) > 0)
.collect(Collectors.toSet());
System.out.println(setNew);
}
private void test03_iterate() {
// java8里可以创建一个无限流,比如下面这个数列,起始值是1,后面每一项都在前一项的基础上 * 2 + 1
Stream<Integer> streamInJava8 = Stream.iterate(1, t -> 2 * t + 1);
// 打印出该数列的前十个: 1,3,7,15,31,63,127,255,511,1023
System.out.println(streamInJava8.limit(10).map(Object::toString).collect(Collectors.joining(",")));
// 从Java9开始,iterate方法可以添加一个判定器,可以用于限制数列范围不超过1000
Stream<Integer> streamFromJava9 = Stream.iterate(1, t -> t < 1000, t -> 2 * t + 1);
// 这里打印的结果是 1,3,7,15,31,63,127,255,511
System.out.println(streamFromJava9.map(Objects::toString).collect(Collectors.joining(",")));
}
}
2.5 Optional增强
可以将Optional对象直接转为stream
Optional.of("Hello openJDK11").stream()
.flatMap(s -> Arrays.stream(s.split(" ")))
.forEach(System.out::println);
可以为Optional对象提供一个默认的Optional对象
System.out.println(Optional.empty()
.or(() -> Optional.of("default"))
.get());
完整示例代码
package jdk11;
import java.util.Arrays;
import java.util.Optional;
/**
* Optional增强
*
* @author zhaochun
*/
public class TestCase05Optional {
public static void main(String[] args) {
TestCase05Optional me = new TestCase05Optional();
me.test01_2stream_default();
}
private void test01_2stream_default() {
// 可以将Optional对象直接转为stream
Optional.of("Hello openJDK11").stream()
.flatMap(s -> Arrays.stream(s.split(" ")))
.forEach(System.out::println);
// 可以为Optional对象提供一个默认的Optional对象
System.out.println(Optional.empty()
.or(() -> Optional.of("default"))
.get());
}
}
2.6 String增强
String方面,针对空白字符(空格,制表符,回车,换行等),提供了一些新的方法。
isBlank
判断目标字符串是否是空白字符。以下结果全部为true
:
// 半角空格
System.out.println(" ".isBlank());
// 全角空格
System.out.println(" ".isBlank());
// 半角空格的unicode字符值
System.out.println("\u0020".isBlank());
// 全角空格的unicode字符值
System.out.println("\u3000".isBlank());
// 制表符
System.out.println("\t".isBlank());
// 回车
System.out.println("\r".isBlank());
// 换行
System.out.println("\n".isBlank());
// 各种空白字符拼接
System.out.println(" \t\r\n ".isBlank());
strip,stripLeading与stripTrailing
去除首尾的空白字符:
// 全角空格 + 制表符 + 回车 + 换行 + 半角空格 + <内容> + 全角空格 + 制表符 + 回车 + 换行 + 半角空格
var strTest = " \t\r\n 你好 jdk11 \t\r\n ";
// strip 去除两边空白字符
System.out.println("[" + strTest.strip() + "]");
// stripLeading 去除开头的空白字符
System.out.println("[" + strTest.stripLeading() + "]");
// stripTrailing 去除结尾的空白字符
System.out.println("[" + strTest.stripTrailing() + "]");
repeat
重复字符串内容,拼接新的字符串:
var strOri = "jdk11";
var str1 = strOri.repeat(1);
var str2 = strOri.repeat(3);
System.out.println(str1);
System.out.println(str2);
// repeat传入参数为1时,不会创建一个新的String对象,而是直接返回原来的String对象。
System.out.println(str1 == strOri);
lines
lines方法用 r 或 n 或 rn 对字符串切割并返回stream对象:
var strContent = "hello java\rhello jdk11\nhello world\r\nhello everyone";
// lines方法用 \r 或 \n 或 \r\n 对字符串切割并返回stream对象
strContent.lines().forEach(System.out::println);
System.out.println(strContent.lines().count());
完整示例代码
package jdk11;
/**
* String增强
*
* @author zhaochun
*/
public class TestCase06String {
public static void main(String[] args) {
TestCase06String me = new TestCase06String();
me.test01_blank();
me.test02_strip();
me.test03_repeat();
me.test04_lines();
}
private void test01_blank() {
// 半角空格
System.out.println(" ".isBlank());
// 全角空格
System.out.println(" ".isBlank());
// 半角空格的unicode字符值
System.out.println("\u0020".isBlank());
// 全角空格的unicode字符值
System.out.println("\u3000".isBlank());
// 制表符
System.out.println("\t".isBlank());
// 回车
System.out.println("\r".isBlank());
// 换行
System.out.println("\n".isBlank());
// 各种空白字符拼接
System.out.println(" \t\r\n ".isBlank());
}
private void test02_strip() {
// 全角空格 + 制表符 + 回车 + 换行 + 半角空格 + <内容> + 全角空格 + 制表符 + 回车 + 换行 + 半角空格
var strTest = " \t\r\n 你好 jdk11 \t\r\n ";
// strip 去除两边空白字符
System.out.println("[" + strTest.strip() + "]");
// stripLeading 去除开头的空白字符
System.out.println("[" + strTest.stripLeading() + "]");
// stripTrailing 去除结尾的空白字符
System.out.println("[" + strTest.stripTrailing() + "]");
}
private void test03_repeat() {
var strOri = "jdk11";
var str1 = strOri.repeat(1);
var str2 = strOri.repeat(3);
System.out.println(str1);
System.out.println(str2);
// repeat传入参数为1时,不会创建一个新的String对象,而是直接返回原来的String对象。
System.out.println(str1 == strOri);
}
private void test04_lines() {
var strContent = "hello java\rhello jdk11\nhello world\r\nhello everyone";
// lines方法用 \r 或 \n 或 \r\n 对字符串切割并返回stream对象
strContent.lines().forEach(System.out::println);
System.out.println(strContent.lines().count());
}
}
2.7 InputStream增强
InputStream提供了一个新的方法transferTo
,将输入流直接传输到输出流:
inputStream.transferTo(outputStream);
完整示例代码
package jdk11;
import java.io.*;
/**
* InputStream增强
*
* @author zhaochun
*/
public class TestCase07InputStream {
public static void main(String[] args) {
TestCase07InputStream me = new TestCase07InputStream();
me.test01_transferTo();
}
private void test01_transferTo() {
var filePath = "/home/work/sources/test/jdk11-test/src/main/resources/application.yml";
var tmpFilePath = "/home/work/sources/test/jdk11-test/src/main/resources/application.yml.bk";
File tmpFile = new File(tmpFilePath);
if (tmpFile.exists() && tmpFile.isFile()) {
tmpFile.delete();
}
try(InputStream inputStream = new FileInputStream(filePath);
OutputStream outputStream = new FileOutputStream(tmpFilePath)) {
// transferTo将 InputStream 的数据直接传输给 OutputStream
inputStream.transferTo(outputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
}
三、模块化开发简介
Java9引入了模块化,Java Platform Module System,java平台模块系统,简称JPMS。
这里使用IDEA说明如何基于module进行开发。
3.1 idea新建java工程
使用idea新建工程module-test-jdk11
,并删除根目录下的src
目录。
3.2 新建Module
选择工程根目录,右键,选择 new -> module:
next,输入module名称:
这里新建了两个module,一个叫core
,一个叫main
。
3.3 开发module并exports可以被外部访问的包
这里选择core,在其src目录下,新建包com.czhao.test.module.core
,并新建classEmployee
:
package com.czhao.test.module.core;
import java.util.Objects;
/**
* @author zhaochun
*/
public class Employee {
private String name;
private int level;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return level == employee.level &&
name.equals(employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, level);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", level=" + level +
'}';
}
}
然后在src目录下,新建module-info.java
(idea支持选择src -> 右键 -> new -> module-info.java):
module core {
exports com.czhao.test.module.core;
}
这里表示通过exports把包com.czhao.test.module.core
暴露出去。
3.4 开发其他需要依赖core
的module
首先,在idea的工程配置中,选择需要依赖core
的module,在其依赖中添加modulecore
:
然后,在modulemain
的src目录下新建module-info.java
:
module main {
requires core;
}
这里使用requires表示引入对modulecore
的依赖。
然后可以在main
的src目录下新建包和对应的类,并使用core
暴露出来的类:
package com.czhao.test.module.main;
import com.czhao.test.module.core.Employee;
/**
* @author zhaochun
*/
public class AppMain {
public static void main(String[] args) {
Employee employee = new Employee();
employee.setName("kobe");
employee.setLevel(99);
System.out.println(employee);
}
}
如果没有依赖core
,这里是无法使用core
里的类Employee
的。
3.5 依赖其他module
java9之后,部分类库想在module中使用的话,也需要在module-info.java
引入它们,比如jdbc的相关类库。
main
的module-info.java
:
module main {
requires core;
requires java.sql;
}
在modulemain
的依赖中添加jdbc驱动包:
然后可以在代码中使用JDBC开发:
package com.czhao.test.module.main;
import com.czhao.test.module.core.Employee;
import java.sql.*;
/**
* @author zhaochun
*/
public class AppMain {
public static void main(String[] args) {
Employee employee = new Employee();
employee.setName("kobe");
employee.setLevel(99);
System.out.println(employee);
try (Connection connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/db_jdk11_test?useUnicode=true&characterEncoding=UTF-8&useSSL=false",
"root",
"xxxxxx");
PreparedStatement ps = connection.prepareStatement("select * from tb_employee")) {
ResultSet rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("employee_name"));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
四、新工具或新功能
从Java9到Java11,陆续提供了一些新的工具或功能。
4.1 REPL交互式编程
Java提供了一个新的工具jshell
,Java终于可以像python,scala等语言那样,交互式演示语法了。
$ /usr/java/jdk-11.0.7+10/bin/jshell
| 欢迎使用 JShell -- 版本 11.0.7
| 要大致了解该版本, 请键入: /help intro
jshell> var str1 = "hello world";
str1 ==> "hello world"
jshell> System.out.println(str1);
hello world
jshell>
4.2 单文件源代码程序的直接执行
一个单文件源代码,即,单独的java文件,有main方法,且只依赖jdk类库以及自己文件内部定义的类,可以直接用java
执行而无需先编译再执行编译后的class文件了。
这对于一些简单的脚本开发是个利好。
zhaochun@zhaochun-T480:/home/work/sources/test/jdk11-test/src/test/java/jdk11$ ll
总用量 44
drwxrwxr-x 2 zhaochun zhaochun 4096 5月 14 15:27 ./
drwxrwxr-x 3 zhaochun zhaochun 4096 5月 12 14:25 ../
-rw-rw-r-- 1 zhaochun zhaochun 2323 5月 14 14:48 TestCase01TypeInference.java
-rw-rw-r-- 1 zhaochun zhaochun 9803 5月 14 10:56 TestCase02HttpClient.java
-rw-rw-r-- 1 zhaochun zhaochun 3384 5月 14 10:55 TestCase03Collection.java
-rw-rw-r-- 1 zhaochun zhaochun 2896 5月 14 15:27 TestCase04Stream.java
-rw-rw-r-- 1 zhaochun zhaochun 717 5月 14 11:04 TestCase05Optional.java
-rw-rw-r-- 1 zhaochun zhaochun 2220 5月 14 12:54 TestCase06String.java
-rw-rw-r-- 1 zhaochun zhaochun 1009 5月 14 13:23 TestCase07InputStream.java
zhaochun@zhaochun-T480:/home/work/sources/test/jdk11-test/src/test/java/jdk11$ /usr/java/jdk-11.0.7+10/bin/java TestCase01TypeInference.java
strBeforeJava10
strFromJava10
a,x
zhaochun@zhaochun-T480:/home/work/sources/test/jdk11-test/src/test/java/jdk11$ ll
总用量 44
drwxrwxr-x 2 zhaochun zhaochun 4096 5月 14 15:27 ./
drwxrwxr-x 3 zhaochun zhaochun 4096 5月 12 14:25 ../
-rw-rw-r-- 1 zhaochun zhaochun 2323 5月 14 14:48 TestCase01TypeInference.java
-rw-rw-r-- 1 zhaochun zhaochun 9803 5月 14 10:56 TestCase02HttpClient.java
-rw-rw-r-- 1 zhaochun zhaochun 3384 5月 14 10:55 TestCase03Collection.java
-rw-rw-r-- 1 zhaochun zhaochun 2896 5月 14 15:27 TestCase04Stream.java
-rw-rw-r-- 1 zhaochun zhaochun 717 5月 14 11:04 TestCase05Optional.java
-rw-rw-r-- 1 zhaochun zhaochun 2220 5月 14 12:54 TestCase06String.java
-rw-rw-r-- 1 zhaochun zhaochun 1009 5月 14 13:23 TestCase07InputStream.java
zhaochun@zhaochun-T480:/home/work/sources/test/jdk11-test/src/test/java/jdk11$
可以看到,该目录下并未生成class文件。
4.3 完全支持Linux容器(包括docker)
在Docker容器中运行Java应用程序一直存在一个问题,那就是在容器中运行的JVM程序在设置内存大小和CPU使用率后,会导致应用程序的性能下降。这是因为Java应用程序没有意识到它正在容器中运行。随着Java10的发布,这个问题总算得以解诀,JVM现在可以识别由容器控制组(cgroups) 设置的约束,可以在容器中使用内存和CPU约束来直接管理Java应用程序,其中包括:
- 遵守容器中设置的内存限制
- 在容器中设置可用的CPU
- 在容器中设置CPU约束
4.4 支持Unicode 10
Unicode 10新增了8518个字符,总计达到了136690个字符。包括56个新的emoji表情符号。
JDK11在java.lang
下增加了4个类来处理:
- CharacterData00.class
- CharacterData01.class
- CharacterData02.class
- CharacterData0E.class
4.5 新支持的加密算法
Java实现了RFC7539中指定的ChaCha20和Poly1305两种加密算法,代替RC4。
RFC7748定义的密钥协商方案更高效,更安全,JDK增加了两个新的接口XECPublicKey
和XECPrivateKey
。
4.6 Low-Overhead Heap Profiling
免费的低耗能飞行记录仪和堆分析仪。
通过JVMTI的SampledObjectAlloc回调提供了一个开销低的heap分析方式提供一个低开销的,为了排错java应用问题,以及JVM问题的数据收集框架。
希望达到的目标如下:
- 提供用于生产和消费数据作为事件的API
- 提供缓存机制和二进制数据格式
- 允许事件配置和事件过滤
- 提供OS,JVM和JDK库的事件
4.7 Flight Recorder
Flight Recorder 源自飞机的黑盒子。 Flight Recorder 以前是商业版的特性,在java11当中开源出来,它可以导出事件到文件中,之后可以用Java Mission Control 来分析。
两种启动方式:
- 可以在应用启动时配置
java -XX:StartFlightRecording
- 应用启动之后,使用jcmd来录制,如下代码:
$ jcmd <pid> JFR.start # 启动记录仪
$ jcmd <pid> JFR.dump.filename=recording.jfr # 将记录内容保存到文件里
$ jcmd <pid> JFR.stop # 停止记录仪
查看jfr文件在java11里没有办法,不过在java12里已经加入了jfr命令,可以查看jfr文件
JFR是一套集成进入JDK、JVM内部的事件机制框架,通过良好架构和设计的框架,硬件层面的极致优化,生产环境的广泛验证,它可以做到极致的可靠和低开销。在SPECjbb2015等基准测试中,JFR的性能开销最大不超过1%,所以,工程师可以基本没有心理负担地在大规模分布式的生产系统使用,这意味着,我们既可以随时主动开启JFR进行特定诊断,也可以让系统长期运行JFR,用以在复杂环境中进行"After-the-fact"分析。还需要苦恼重现随机问题吗? JFR让问题简化了很多在保证低开销的基础上, JFR提供的能力也令人眼前一亮,例如:我们无需BCI就可以进行Object Allocation Profiling, 终于不用担心BTrace 之类把进程搞挂了。对锁竞争、阻塞、延迟,JVM GC、SafePoint 等领域,进行非常细粒度分析。甚至深入JIT Compiler 内部,全面把握热点方法、内联、逆优化等等。JFR提供了标准的Java,C++ 等扩展API,可以与各种层面的应用进行定制、集成,为复杂的企业应用栈或者复杂的分布式应用,提供All-in-One 解决方案。而这一切都是内建在JDK和JVM内部的,并不需要额外的依赖,开箱即用。
五、垃圾回收器
Java11新增了两种垃圾回收器,并改善了Java8开始提供的G1垃圾回收器。
关于Java8到Java11的垃圾回收器,将在后续其他文章中详细介绍。
5.1 ZGC
Experimental(实验性质),生产环境不建议使用
ZGC是Java11最引人瞩目的新特性。
启用方法:-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
说明:ZGC, A Scalable Low-Latency Garbage collector( Experimental) ,一个可伸缩的低延时的垃圾回收器。GC暂停时间不会超过10ms,既能处理几百兆的小堆,也能处理几个T的大堆。和G1相比,应用吞吐能力不会下降超过15%,为未来的GC功能和利用colord指针以及Load barriers 优化奠定了基础。初始只支持64位系统。
ZGC的设计目标是:支持TB级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于15%。将来还可以扩 展实现机制,以支持不少令人兴奋的功能,例如多层堆(即热对象置于DRAM和冷对象置于NVMe闪存),或压缩堆。
GC是java主要优势之一。然而,当GC停顿太长,就会开始影响应用的响应时间。消除或者减少GC停顿时长,java将有可能在更广泛的应用场景中成长为一个更有吸引力的平台。此外,现代系统中可用内存不断增长,用户和程序员希望JVM能够以高效的方式充分利用这些内存,并且无需长时间的GC暂停时间。
ZGC是一个并发,基于region,压缩型的垃圾收集器,只有root扫描阶段会STW,因此GC停顿时间不会随着堆的增长和存活对象的增长而变长。
垃圾回收器 | 平均等待时间(ms) | 最大等待时间(ms) |
---|---|---|
ZGC | 1.091 | 1.681 |
G1 | 156.806 | 543.846 |
5.2 Epsilon
Experimental(实验性质),生产环境不建议使用
启用方法:-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
说明:开发一个处理内存分配但不实现任何实际内存回收机制的GC,一旦可用堆内存用完,JVM就会退出,如果有System.gc()
调用,实际上什么也不会发生(这种场景下和-XX:+DisableExplicitGC
效果一样), 因为没有内存回收,这个实现可能会警告用户尝试强制GC是徒劳的。
主要用途如下:
- 性能测试(它可以帮助过滤掉GC引起的性能假象)
- 内存压力测试(例如,知道测试用例应该分配不超过1GB的内存,我们可以使用
-Xmx1g -XX:+UseEpsilonGC
,如果程序有问题,则程序会崩溃。 - 非常短的JOB任务(对于这种任务,GC是在浪费资源)
- VM接口测试
- Last-drop延迟&吞吐改进
5.3 更好的G1
对于G1 GC,相比于JDK8,升级到JDK 11即可免费享受到:并行的Full GC,快速的CardTable扫描,自适应的堆占用比例调整(IHOP),在并发标记阶段的类型卸载等等。这些都是针对G1的不断增强,其中串行FullGC等甚至是曾经被广泛诟病的短板,你会发现GC配置和调优在JDK11中越来越方便。
六、移除与不再推荐使用的类库或功能
Java9到Java11,陆续移除了一些类库或功能。
6.1 移除了Java EE和CORBA Moudles
在java11中移除了不太使用的JavaEE模块和CORBA技术。
CORBA来自于二十世纪九十年代,Oracle认为,现在用CORBA开发现代Java应用程序已经没有意义了,维护CORBA的成本已经超过了保留它带来的好处。
但是删除CORBA将使得那些依赖于JDK提供部分CORBAAPI的CORBA实现无法运行。目前还没有第三方CORBA版本,也不确定是否会有第三方愿意接手CORBA API的维护工作。
在java11中将java9标记废弃的Java EE及CORBA模块移除掉,具体如下:
xml 相关被移除的:
- java.xml.ws
- java.xml.bind
- java.xml.ws
- java.xml.ws.annotation
- jdk.xml.bind
- jdk.xml.ws
只剩下java.xml, java.xml.crypto.jdk.xml.dom 这几个模块。
其它被移除的Java EE和CORBA相关类库:
- java.corba
- java.se.ee
- java.activation
- java.transaction(但是java11新增了一个java.transaction.xa模块)
6.2 其他移除的类库
- com.sun.awt.AWTUtilities
- sun.miss.Unsafe.defineClass
- Thread.destroy() 以及 Thread.stop(Throwable) 方法
- sun.nio.ch.disableSystemWideOverlappingFileLockCheck 属性
- sun.locale.formatasdefault 属性
- jdk snmp 模块
- javafx
- java Mission Control
- Root Certificates: 一些根证书被移除:Baltimore Cybertrust Code Signing CA, SECOM Root Certificate, AOL and Swisscom Root Certificates
其中,使用java.lang.invoke.MethodHandles.Lookup.defineClass
来替代移除的sun.miss.Unsafe.defineClass
。
6.3 将Nashorn Javascript标记为不推荐
将Javascript引擎标记为Deprecate,后续版本会移除,有需要的可以考虑使用开源的GraalVM。
6.4 将Pack200 Tools and API标记为不推荐
java11中将pack200以及unpack200工具以及java.tiljar中的Pack200 API标记为Deprecate。因为Pack200主要是用来压缩jar包的工具,由于网络下载速度的提升以及java9引入模块化系统之后不再依赖Pack200,因此这个版本将其标记为Deprecate。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。