Java11已经于2018年9月25日发布,支持期限至2026年9月,这是Java 8之后的首个LTS(长期支持版),本篇学习并记录下该版本的新特性。

模块化系统(Project Jigsaw) JEP 261

这是 Java 9 最核心的变化。它允许开发者将代码划分为模块(Module),明确定义导出哪些包以及依赖哪些模块。其目的就是解决classpath混乱问题,减少JRE体积。

其用法就是在项目根目录创建module-info.java,内容为

1
2
3
4
module cn.net.dev {
requires java.sql; // 依赖 SQL 模块
exports cn.net.dev.api; // 只对外暴露 api 包
}

JShell (REPL) JEP 222

scala一样,Java现在也支持REPL了。

用法:直接在终端中输入jshell即可。

1
2
3
4
5
jshell> String s = "Hello World";
s ==> "Hello World"

jshell> System.out.println(s);
Hello World

私有接口方法

Java 8 引入了默认方法和静态方法,Java 9 允许在接口中定义私有方法,用于抽取多个默认方法中的公共逻辑。

1
2
3
4
5
6
7
8
9
10
11
package cn.net.dev;

public interface MyService {
default void doWork() {
log("start");
}
//私有方法
private void log(String msg) {
System.out.println(msg);
}
}

局部变量类型推断 JEP286

允许使用var声明局部变量,由编译器自动推断类型。(PS:越来越像Scala了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.net.dev;

import java.util.List;

public class VarDemo {
public static void main(String[] args) {
var list = List.of("Java 8", "Java 9", "Java 10", "Java 11");
var stream = list.stream();
stream.forEach(System.out::println);
for (var item : list) {
System.out.println(item);
}
}
}

标准HTTP Client JEP 321

正式取代了过时的 HttpURLConnection,原生支持 HTTP/2WebSocket,且支持异步非阻塞操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package cn.net.dev;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class HttpDemo {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://dev.net.cn"))
.build();
// 异步发送请求
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
}
}

单文件直接运行 JEP 330

无需通过 javac 编译生成 .class 文件,可以直接运行 .java 源文件。(PS:为啥不用python呢?),这让我想起了大学时期看马士兵的Java视频,每个类都要javac xxx.java, java xxx运行。不过现在都用IDE了,感觉不出来。

String类新增API

Java 11 增强了对字符串的处理能力,特别是处理空白字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.net.dev;

public class StringNewApi {
public static void main(String[] args) {
String str = " dev.net.cn ";
// 是否为空或仅含空格
System.out.println(str.isBlank());
// 比 trim() 更智能的去空格
System.out.println(str.strip());
// stream处理
System.out.println("Line\nLine".lines().count());
// 打印三次Hi, scala "hi" * 3
System.out.println("Hi ".repeat(3));
}
}

集合增强:toArray(IntFunction)

从集合转数组更优雅

1
2
3
var list = List.of("a", "b");
// Java 11 之前的语法更复杂
String[] array = list.toArray(String[]::new);

集合工厂方法 JEP269

在 Java 9 之前,创建一个包含初始元素的不可变集合非常麻烦(需要 Arrays.asList 或多次 add)。现在只需一行代码。

注意:这些方法返回的是真正不可变的集合,尝试 add 会抛出异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package cn.net.dev;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class CollectionFactoryDemo {
public static void main(String[] args) {
// Java 9之前
//List<String> list = Arrays.asList("a", "b", "c");
// java9之后
var list = List.of("Java","Python","Scala");
var set = Set.of(".java",".py",".scala");
var map = Map.of("name","zhangsan","age","22");

var complexMap =Map.ofEntries(
Map.entry("name","zhangsan"),
Map.entry("age",22)
);
System.out.println(list);
//Exception in thread "main" java.lang.UnsupportedOperationException
// at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:72)
// at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:76)
// at cn.net.dev.CollectionFactoryDemo.main(CollectionFactoryDemo.java:23)
//list.add("Go");
}
}

Stream API增强

Java 9 为 Stream 引入了四个非常实用的方法,解决了带条件的流截断问题。

  • takeWhile: 一旦条件不成立,立即停止处理后续元素。
  • dropWhile: 一旦条件不成立,开始处理后续所有元素。
  • iterate: 支持了类似 for 循环的终止条件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package cn.net.dev;

import java.util.stream.Stream;

public class StreamEnhanceDemo {
public static void main(String[] args) {
// 1.takeWhile 只要数字小于5就拿走
Stream.of(1, 2, 3, 6, 4, 5)
.takeWhile(n -> n < 5)
// 输出123,碰到6就停止,哪怕后面是4
.forEach(System.out::println);
System.out.println("------".repeat(3) );
//等同于for(int i=0;i<10;i+=2),iterate: 新增了终止判断 (种子, 谓词, 下一个值)
//可读性不如for,用来炫技还不错。
Stream.iterate(0, n -> n < 10, n -> n + 2)
.forEach(System.out::println);
}
}

Optional 增强

增加了对“空值处理”的流式支持,避免了繁琐的 ifPresent 嵌套。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package cn.net.dev;

import java.util.Optional;

public class OptionalDemo {
public static void main(String[] args) {
Optional<String> optional = Optional.ofNullable(null);
// or() 如果为空,返回另一个Optional
String result = optional.or(() -> Optional.of("默认值")).get();
// ifPresentOrElase() 如果有值,则执行A,否则执行B
optional.ifPresentOrElse(
System.out::println,
() -> System.out.println("没找到数据")
);
// stream() 将Optional转为Stream,方便链式调用
long count = optional.stream().count();
}
}

容器感知与资源管理

1. 容器感知 (Container Awareness)

  • Java 10 引入:在此之前,Java 在 Docker 容器内无法准确识别容器限制的 CPU 和内存(它会看物理机的配置),导致内存溢出。
  • Java 10 改正了这一点:JVM 现在能正确识别 CGroup 限制。

2. 并行完全 GC 的 G1 (Java 10)

G1 垃圾回收器在 Java 9 成为默认 GC。Java 10 进一步优化了它,使 Full GC 能够并行执行。如果你的服务器内存较大(如 8G 以上),这能大幅缩短 Full GC 的卡顿时间。

低延迟垃圾回收器 Epsilon & ZGC

这两个都是实验性的,了解下。

1. Epsilon GC (JEP 318 - 实验性但正式发布)

它是一个“不做任何事”的 GC。它只负责分配内存,不负责回收。

  • 用途:性能测试、极短寿命的任务(执行完就退出的脚本)。
  • 参数-XX:+UseEpsilonGC

2. ZGC 的诞生 (Java 11 - 实验性引入)

虽然在 Java 11 中它是实验性的(Linux AArch64/x64),但它标志着 Java 进入了 亚毫秒级停顿 时代的起点。

Try-with-resources优化

在Java 7中的Try-with-resources,需要将变量名在try定义。

1
2
3
4
5
6
7
8
9
10
try (FileInputStream is = new FileInputStream("a.txt");
FileOutputStream os = new FileOutputStream("b.txt");) {
int c;
while ((c = is.read()) != -1) {
System.out.print((char) c);
os.write((char) c);
}
} catch (IOException e) {
e.printStackTrace();
}

在Java 9中,做了一丢丢语法改进,允许资源声明在try块外部。这意味着资源可以在try块外部声明,但在try块中使用时,Java仍然会自动关闭这些资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
InputStreamReader reader = new InputStreamReader(System.in);
OutputStreamWriter writer = new OutputStreamWriter(System.out);
try (reader;
writer) {
char[] buffer = new char[20];
int len = reader.read(buffer);
if (len != -1) {
String str = new String(buffer);
System.out.println(str);
writer.write(str);
}
} catch (IOException e) {
e.printStackTrace();
}

下面补充下Try-With-Resources的一些注意事项。

问题 答案
Try-With-Resources 适用于哪些资源? Try-With-Resources适用于实现了AutoCloseable接口的资源,如InputStreamOutputStreamFileReaderFileWriter等。
Java 9中资源可以在try块外部声明吗? 是的,Java 9允许资源在try块外部声明,但在try块中使用时,Java仍然会自动关闭这些资源。
Try-With-Resources中资源声明的顺序有影响吗? 是的,资源声明的顺序会影响它们的关闭顺序。资源会按照声明的逆序关闭,即后声明的资源先关闭。
Try-With-Resources中资源可以重新赋值吗? 不可以,Try-With-Resources中资源声明为final,不能在try块中重新赋值。
Try-With-Resources可以处理多个资源吗? 是的,可以在try块的括号中使用分号分隔多个资源声明,Java会自动关闭所有资源。

响应式流(Reactive Streams) JEP266

Java 9 引入了 java.util.concurrent.Flow 类,这是对 响应式编程 的官方标准化。

它提供了发布者(Publisher)和订阅者(Subscriber)的模型,支持“背压”(Backpressure),即订阅者可以告诉发布者:“慢点发,我处理不过来了”。是 Java 生态中 Project Reactor (Spring WebFlux) RxJava能够互通的基石。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package cn.net.dev;

import java.time.Duration;
import java.util.concurrent.Flow.*;
import java.util.concurrent.SubmissionPublisher;

public class FlowDemo {
public static void main(String[] args) {
try (SubmissionPublisher<String> publisher = new SubmissionPublisher<>();) {
Subscriber<String> subscriber = new Subscriber<String>() {
private Subscription subscription;

@Override
public void onSubscribe(Subscription s) {
this.subscription = s;
s.request(1); // 请求 1 条数据
}

@Override
public void onNext(String item) {
System.out.println("收到: " + item);
subscription.request(1); // 处理完再请求下一条
}

@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
}

@Override
public void onComplete() {
System.out.println("完成!");
}
};
publisher.subscribe(subscriber);
publisher.submit("Data 1");
publisher.submit("Data 2");

Thread.sleep(Duration.ofSeconds(1).toMillis());
} catch (Exception e) {
e.printStackTrace();
}
}
}

堆栈遍历 API (Stack-Walking API) JEP 259

在 Java 9 之前,获取堆栈信息(如 Thread.getStackTrace())开销巨大,因为它必须捕获整个快照。

  • 改进StackWalker 允许以 Stream 的方式按需遍历堆栈,找到你关心的那一层就停止,性能极高。
  • 用途:编写高性能的日志框架或安全检查工具。
1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.net.dev;

public class StackWalkerDemo {
public static void main(String[] args) {
StackWalker walker = StackWalker.getInstance();
walker.forEach(f -> {
System.out.println(f.getClassName() + " -> " + f.getMethodName());
});
}
}

// 输出
cn.net.dev.StackWalkerDemo -> main

多版本兼容 JAR (Multi-Release JARs) JEP 238

允许在一个 JAR 包中针对不同的 Java 版本包含不同的类文件。

  • 结构:在 META-INF/versions/ 目录下存放特定版本的 .class

统一JVM日志(Unified JVM Logging) JEP 158

Java 9 统一了所有 JVM 组件的日志格式和配置方式,不再是以前乱七八糟的参数。

  • 用法:使用 -Xlog 参数。
1
2
3
4
# 查看所有的日志标签
java -Xlog:help
# 监控 GC 详细信息并输出到文件
java -Xlog:gc*:file=gc.log:time,level,tags

低级并发握手 (Thread-Local Handshakes) JEP 312

这是一个非常底层但对性能影响巨大的优化。

  • 旧版:为了执行某些操作(如垃圾回收标记),JVM 必须触发 全局安全点 (Global Safepoint),让所有线程停下来。
  • 新版:允许在不停止所有线程的情况下,对单个线程执行回调操作。
  • 结果:显著减少了系统的整体停顿时间(STW)。

飞行记录器(Java Flight Recorder) JEP 328

JFR 原本是商业收费功能(JRockit/Oracle JDK),在 Java 11 中正式开源并免费。它是 JVM 内置的“黑匣子”,可以以极低的开销(通常小于 1%)记录 JVM 的详细运行轨迹。

1
2
# 启动时开启记录
java -XX:StartFlightRecording=duration=60s,filename=myrecording.jfr -jar app.jar

可以使用 JDK Mission Control (JMC) 打开生成的文件,直观地看到 CPU 占用、内存分配热点和线程死锁。

变量句柄 (Variable Handles) JEP 193

这是为了取代 sun.misc.Unsafe 中常用的操作。它提供了一种标准、强类型、安全的方式来操纵变量,包括原子更新(如 CAS 操作)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package cn.net.dev;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;

public class VarHandleDemo {
private volatile int status = 0;
private static final VarHandle HANDLE;

static {
try {
HANDLE = MethodHandles.lookup()
.findVarHandle(VarHandleDemo.class, "status", int.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}

public void safeUpdate() {
boolean success = HANDLE.compareAndSet(this, 0, 1);
System.out.println("更新是否成功:" + success);
}
}

统一内存段访问(Compact Strings) JEP 254

在 Java 9 之前,String 内部用 char[] 存储(每个字符 2 字节)。Java 9 改为 byte[] 加上一个编码标记位。如果你的字符串主要是 ASCII 字符(例如: Nginx 配置里的路径、博客里的英文),内存占用直接减少 50%

数据清洗器(Clearners) JEP 277

官方正式废弃了 Object.finalize()(它会导致严重的性能问题和不确定性),取而代之的是 java.lang.ref.Cleaner。它比 finalize 更安全,因为清理动作是在独立的线程中执行的,不会拖累 GC 线程。

Graal编译器(JIT优化) JEP317

Java 10 引入了实验性的 Graal 编译器作为 C2 编译器的替代品(用 Java 编写的即时编译器)。虽然在 Java 11 中它仍是实验性的,但它开启了 Java 向 AOT(提前编译) 进化的道路,也是现在流行的 Quarkus 等云原生框架的核心。

开启参数:

1
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler

核心库的一些API

Optional.isEmpty()

在 Java 11 之前只有 isPresent()。现在你可以写 if (opt.isEmpty()),逻辑上更直观。

Predicate.not()

方便在流操作中做取反。

1
2
// 过滤掉非空字符串
list.stream().filter(Predicate.not(String::isEmpty)).collect(...);

ChaCha20 和 Poly1305 加密算法 (JEP 329)

Java 11 原生支持了这些现代加密算法。