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; exports cn.net.dev.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/2 和 WebSocket,且支持异步非阻塞操作。
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()); System.out.println(str.strip()); System.out.println("Line\nLine".lines().count()); System.out.println("Hi ".repeat(3)); } }
|
集合增强:toArray(IntFunction)
从集合转数组更优雅
1 2 3
| var list = List.of("a", "b");
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) { 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); } }
|
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) { Stream.of(1, 2, 3, 6, 4, 5) .takeWhile(n -> n < 5) .forEach(System.out::println); System.out.println("------".repeat(3) ); 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); String result = optional.or(() -> Optional.of("默认值")).get(); optional.ifPresentOrElse( System.out::println, () -> System.out.println("没找到数据") ); 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接口的资源,如InputStream、OutputStream、FileReader、FileWriter等。 |
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); }
@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 组件的日志格式和配置方式,不再是以前乱七八糟的参数。
1 2 3 4
| java -Xlog:help
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 原生支持了这些现代加密算法。