第2章 Java 版本演变史:从 1996 年到 2026 年
第二章 Java 版本演变史:从 1996 年到 2026 年
「如果你在 1996 年告诉别人,2026 年这门语言还会是最流行的编程语言之一,他们可能会觉得你疯了。但 Java 就是这么倔强——别人写胶水代码,它写企业级系统;别人追新特性,它稳扎稳打地演进。Java 的历史,就像一部编程界的『活着的传奇』,每十年一次大变局,每次变局都让程序员又爱又恨。」
2.1 Java 1.0 ~ Java 1.4(1996~2002):蛮荒时代
1996 年 1 月,Sun Microsystems 发布了 Java 1.0。这门语言最初被设计用来做智能家电的控制程序,结果阴差阳错地成为了互联网时代的宠儿。那时的 Java,就像一个刚进城的农村青年——土气、朴实,但浑身是劲。
2.1.1 Applet 的兴衰:在浏览器里运行 Java 的梦想
Applet 是 Java 第一个「网红」特性。它允许 Java 程序嵌入到网页中,在浏览器里运行。1996 年,当你打开一个网页,看到一个会动的动画或交互式图表,那大概率就是 Applet 的功劳。
1
2
3
4
5
6
7
8
9
10
11
| // 一个经典的 Applet 示例
import java.applet.Applet;
import java.awt.Graphics;
public class HelloApplet extends Applet {
@Override
public void paint(Graphics g) {
// 在网页上画出「Hello, World!」
g.drawString("Hello, Applet World!", 20, 30);
}
}
|
Applet 的核心理念很超前:「一次编写,到处运行」(Write Once, Run Anywhere)。你的程序可以在 Windows、Mac、Linux 的浏览器里同时运行,不用担心兼容性问题。这听起来是不是很像今天的 WebAssembly?
但 Applet 的问题也很明显:需要浏览器安装 Java 插件、安全限制让人抓狂、加载速度慢如蜗牛。随着 HTML5、CSS3 和 JavaScript 的崛起,Applet 逐渐被淘汰。下面这条时间线值得记准确:
- 2015 年 9 月:Chrome 45 移除了 NPAPI 插件支持,Java 插件就此在 Chrome 中失效(不是 2016 年)
- 2017 年:JDK 9 正式把 Applet API 标记为废弃(JEP 289)
- 2018 年:JDK 11 移除了 JDK 自带的浏览器插件与 Java Web Start 部署栈(不是"Java 11 废弃 Applet API")
- 2021 年:JDK 17 通过 JEP 398 把 Applet API 标记为"废弃并计划移除"
Applet 成了 Java 历史上第一个「被拍死在沙滩上」的前浪。顺带说一句:java.applet 包在 JDK 25 里仍然存在,只是带着"已过时、待删除"的标记,所以今天用 javac 编译上面的示例只会收到一条警告。
小知识:Applet 的遗产影响深远。后来 Android 的 App 运行环境、Java Web Start 技术,都能看到 Applet 的影子。
2.1.2 JDBC 的诞生:Java 连接数据库
在 Java 出现之前,连接数据库是个头疼的问题。每种数据库都有自己的 API——连接 MySQL 用一套,连接 Oracle 用另一套,程序员需要写大量重复代码。JDBC(Java Database Connectivity)的出现,彻底改变了这一切。
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
| import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class JdbcDemo {
public static void main(String[] args) {
// 有了 JDBC,不管你用 MySQL、Oracle 还是 PostgreSQL
// 代码风格基本一致,这就是「统一的数据库访问接口」
String url = "jdbc:mysql://localhost:3306/mydb";
String user = "root";
String password = "123456";
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
while (rs.next()) {
System.out.println("用户名: " + rs.getString("name"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
JDBC 的设计理念非常优雅:定义一套接口,让数据库厂商自己去实现。程序员只需要学会 JDBC 这套 API,就能操作任何支持 JDBC 的数据库。这,就是面向接口编程的魅力。
2.1.3 Servlet 的出现:Java 做网站的起点
1997 年,Java Servlet API 发布,Java 正式进军 Web 开发领域。在那个 PHP 和 ASP 统治 web 开发的年代,Servlet 以其「跨平台」和「稳定性」的优势,逐渐成为企业级 Web 开发的首选。
// 这个例子需要引入 Jakarta Servlet API(jakarta.servlet-api)才能编译,
// 单文件直接 javac 会因为找不到包而报错,这里只做概念演示。
import java.io.IOException;
import java.io.PrintWriter;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
// Servlet 是 Java Web 开发的基础,所有框架都建立在其之上
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("<html><body>");
out.println("<h1>Hello, Servlet!</h1>");
out.println("</body></html>");
}
}
Servlet 的出现为后来 Java Web 框架的繁荣奠定了基础。Struts、Spring MVC、Hibernate 这些大名鼎鼎的框架,都是站在 Servlet 的肩膀上发展起来的。可以说,没有 Servlet,就没有今天 Java 在 Web 领域的地位。
2.1.4 JNDI、JavaMail、EJB——企业 Java 的雏形
1999 年,Java 进入了企业计算领域。Sun 公司推出了一整套企业级 API,构成了 Java EE(当时还叫 J2EE)的核心:
JNDI(Java Naming and Directory Interface):目录服务接口,简单说就是「帮你找东西的 API」。通过 JNDI,你可以找到数据库连接池、邮件服务器、企业内部的分布式服务等。想象一下,它就像一个电话本,你不用记住每个服务的具体地址,只需要记住它们的名字,JNDI 帮你查。
JavaMail:处理电子邮件的 API,支持发送邮件(SMTP)、接收邮件(POP3/IMAP)。2000 年前后,邮件是企业通信的主流方式,JavaMail 让程序发送邮件变得轻而易举。
EJB(Enterprise JavaBeans):一种分布式组件规范,用于构建企业级应用。EJB 的理念很超前:业务逻辑封装成组件,部署到服务器上,多个应用可以共享调用。但说实话,EJB 有点过于复杂,配置 XML 能写到你怀疑人生。这也间接促成了 Spring 框架的崛起——「轻量级」的 Spring 对比「重量级」的 EJB,就像电动车对比燃油车。
2.2 Java 5 / Java 1.5(代号 Tiger,2004):语法革命
如果说 Java 1.0 到 1.4 是「蛮荒时代」,那么 Java 5 就是「文艺复兴」。这一版的更新力度之大,堪称 Java 历史上最重要的一次版本升级,没有之一。
Java 5 引入了大量革命性的语法特性,让 Java 从一门「过程式面向对象语言」彻底蜕变为「现代编程语言」。很多人说「Java 8 是最重要的版本」,但 Java 5 才是真正的奠基者——没有泛型、没有注解、没有枚举,Java 8 的 Lambda 就是空中楼阁。
2.2.1 泛型(Generics):写 List<String> 而不是 List,让代码更安全
在泛型出现之前,List 可以存放任何类型的对象,取出来的时候需要强制类型转换,还容易出现 ClassCastException。
1
2
3
4
5
6
7
8
9
| // 泛型之前的代码(危险操作,需要强制类型转换)
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0); // 需要强制转换,万一放错了类型就炸
// 泛型之后的代码(安全、优雅)
List<String> list = new ArrayList<>();
list.add("Hello");
String s = list.get(0); // 不需要强制转换,编译器帮你检查类型
|
泛型的核心思想是**「参数化类型」**——List<String> 中的 String 就是类型参数。有了泛型,编译器能在编译阶段发现类型错误,而不是等到运行时才崩溃。泛型让集合操作变得既安全又简洁,是 Java 类型系统的一次重大升级。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // 泛型方法示例
public static <T> T getFirst(List<T> list) {
if (list == null || list.isEmpty()) {
return null;
}
return list.get(0);
}
// 泛型接口示例(注意别和 JDK 自带的 java.lang.Comparable 撞名,这里叫 MyComparable)
public interface MyComparable<T> {
int compareTo(T other);
}
// 多个类型参数的泛型(同样避免和 java.util.HashMap 撞名)
public class MyMap<K, V> {
// K 是键的类型,V 是值的类型
}
|
2.2.2 注解(Annotations):@Override、@Deprecated——代码的元数据
注解是 Java 5 引入的「语法糖」,它允许你给代码添加元数据(metadata)。简单理解,注解就是「写在代码上的标签」。
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
| import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class AnnotationDemo {
// 自定义注解:RUNTIME 表示运行时仍可反射读取,METHOD 表示只能标在方法上
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String value() default "默认值"; // 注解的属性,可以带默认值
int count() default 1;
}
// @Override 告诉编译器:这个方法是重写的父类方法。
// 如果方法名拼错、或返回值与父类不一致,编译器会直接报错。
@Override
public String toString() {
return "AnnotationDemo";
}
// @Deprecated 标记这个方法已经过时,别人调用时编译器会给出警告
@Deprecated
public void oldMethod() {
}
// 使用自定义注解
@MyAnnotation(value = "测试", count = 3)
public void annotatedMethod() {
System.out.println("这个方法被我注解了!");
}
public static void main(String[] args) {
AnnotationDemo demo = new AnnotationDemo();
System.out.println(demo); // AnnotationDemo
demo.annotatedMethod(); // 这个方法被我注解了!
}
}
|
注解的用途非常广泛:Spring 用来做依赖注入、JUnit 用来标记测试方法、Hibernate 用来映射数据库表……没有注解,现代 Java 框架至少少一半威力。
2.2.3 枚举(Enum):定义有限常量的优雅方式
在 Java 5 之前,定义常量是这样的:
1
2
3
4
5
| // 苦逼的常量定义方式
public static final int SPRING = 1;
public static final int SUMMER = 2;
public static final int AUTUMN = 3;
public static final int WINTER = 4;
|
这种方式容易写错(比如 if (season == 5) 编译器不会报错),也没有类型安全。枚举出现后:
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
45
46
47
48
49
| public class SeasonDemo {
// 枚举:类型安全、有属性、有方法
public enum Season {
SPRING("春天", 1),
SUMMER("夏天", 2),
AUTUMN("秋天", 3),
WINTER("冬天", 4);
private final String chineseName;
private final int order;
Season(String chineseName, int order) {
this.chineseName = chineseName;
this.order = order;
}
public String getChineseName() { return chineseName; }
public int getOrder() { return order; }
}
public static void main(String[] args) {
Season s = Season.SPRING;
// 传统 switch 写法(Java 5 起就支持),别忘了一个个 break
switch (s) {
case SPRING:
System.out.println("春暖花开");
break;
case SUMMER:
System.out.println("烈日炎炎");
break;
case AUTUMN:
System.out.println("秋高气爽");
break;
case WINTER:
System.out.println("冰天雪地");
break;
}
// Java 14+ 的箭头形式更简洁,不需要 break,也不会意外穿透
switch (s) {
case SPRING -> System.out.println("春暖花开");
case SUMMER -> System.out.println("烈日炎炎");
case AUTUMN -> System.out.println("秋高气爽");
case WINTER -> System.out.println("冰天雪地");
}
}
}
|
枚举不仅仅是常量集合,它还是一个完整的类——可以有属性、方法、构造函数,甚至可以实现接口。Java 的枚举还能被用于 switch 语句、作为 Map 的 key、放入集合中,简直是万能选手。
2.2.4 自动装箱/拆箱(Auto Boxing/Unboxing):int ↔ Integer 自动转换
Java 5 之前,基本类型和包装类型之间的转换需要手动进行:
1
2
3
4
| // 手动装箱:把 int 转成 Integer
int primitive = 42;
Integer wrapper = Integer.valueOf(primitive); // 手动装箱
int back = wrapper.intValue(); // 手动拆箱
|
Java 5 引入了自动装箱/拆箱,编译器自动帮你完成转换:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // 自动装箱:编译器自动转换为 Integer.valueOf(42)
Integer boxed = 42;
// 自动拆箱:编译器自动转换为 boxed.intValue()
int unboxed = boxed;
// 集合里只能放对象,int 会自动装箱
List<Integer> numbers = new ArrayList<>();
numbers.add(1); // 自动装箱:Integer.valueOf(1)
numbers.add(2);
numbers.add(3);
int sum = 0;
for (Integer n : numbers) {
sum += n; // 自动拆箱:n.intValue()
}
System.out.println("Sum = " + sum);
|
看起来很方便,但有个坑要注意:自动装箱可能产生额外的对象,导致性能问题。比如:
1
2
3
4
5
6
| // 性能陷阱:循环中的自动装箱
Long sum = 0L; // 注意是 Long,不是 long
for (int i = 0; i < 1000000; i++) {
sum += i; // 每次 += 都会创建一个新的 Long 对象!
}
// 正确做法:用 long sum = 0L;
|
2.2.5 foreach 循环:for (Type var : collection) 更简洁
传统的 for 循环写起来有点啰嗦:
1
2
3
4
5
6
7
8
9
10
| // 传统 for 循环
for (Iterator<String> i = list.iterator(); i.hasNext(); ) {
String s = i.next();
System.out.println(s);
}
// 更简洁的写法
for (String s : list) {
System.out.println(s);
}
|
foreach(增强 for 循环)既可以遍历数组,也可以遍历实现了 Iterable 接口的集合。它让代码更简洁、可读性更高,但有个限制:无法获取当前元素的索引(如果需要,用传统 for 循环)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // 遍历数组
String[] names = {"Alice", "Bob", "Charlie"};
for (String name : names) {
System.out.println(name);
}
// 遍历集合
List<String> namesList = Arrays.asList("Alice", "Bob", "Charlie");
for (String name : namesList) {
System.out.println(name);
}
// 遍历 Map(需要用 entrySet)
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 25);
ages.put("Bob", 30);
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
|
2.2.6 可变参数(Varargs):String... args,参数数量可变
可变参数允许你传入零个或多个同类型的参数。在 printf 风格的 API 中特别有用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // 可变参数方法
public static void printAll(String... names) {
for (String name : names) {
System.out.println(name);
}
}
// 调用方式很灵活
printAll(); // 0 个参数
printAll("Alice"); // 1 个参数
printAll("Bob", "Charlie"); // 2 个参数
printAll("David", "Eve", "Frank", "Grace"); // 多个参数
// 可变参数实际上就是个语法糖,底层是数组
// 编译器会把 String... names 变成 String[] names
|
可变参数让方法调用更灵活,尤其适合「参数数量不确定」的场景,比如日志记录、错误处理、格式化输出等。
2.2.7 静态导入(Static Import):import static java.lang.Math.*;
静态导入让你可以直接使用静态方法或静态字段,而不用写类名:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // 没有静态导入之前
double area = Math.PI * Math.pow(radius, 2);
double sinValue = Math.sin(Math.PI / 2);
double maxValue = Math.max(Math.max(a, b), c);
// 使用静态导入之后
import static java.lang.Math.PI;
import static java.lang.Math.pow;
import static java.lang.Math.sin;
import static java.lang.Math.max;
double area = PI * pow(radius, 2);
double sinValue = sin(PI / 2);
double maxValue = max(max(a, b), c);
// 或者直接 import static java.lang.Math.*; 导入 Math 所有静态成员
|
静态导入虽好,但别滥用——如果两个类有同名的静态方法,静态导入会让你分不清哪个是哪个。
2.2.8 并发包(J.U.C):java.util.concurrent,并发编程的基础
Java 5 引入了 java.util.concurrent 包,简称 J.U.C。这是 Java 并发编程的一次革命,提供了大量高质量的并发工具类:
ExecutorService:线程池,告别 new Thread() 的苦海CountDownLatch:倒计时门闩,多线程协调CyclicBarrier:循环栅栏,线程间的同步点Semaphore:信号量,控制对资源的访问数量ConcurrentHashMap:线程安全的 HashMap,并发性能秒杀 Hashtable
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
| import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class ConcurrentDemo {
public static void main(String[] args) throws InterruptedException {
// 创建一个固定大小的线程池,比如同时执行 3 个任务
ExecutorService executor = Executors.newFixedThreadPool(3);
// CountDownLatch:等所有线程完成后再汇总结果
CountDownLatch latch = new CountDownLatch(3);
// 提交 3 个任务
executor.submit(() -> {
System.out.println("任务 A 完成");
latch.countDown(); // 计数减一
});
executor.submit(() -> {
System.out.println("任务 B 完成");
latch.countDown();
});
executor.submit(() -> {
System.out.println("任务 C 完成");
latch.countDown();
});
// 等待所有任务完成
latch.await(10, TimeUnit.SECONDS);
// 关闭线程池
executor.shutdown();
System.out.println("所有任务完成,主线程继续执行");
}
}
|
J.U.C 的出现,让 Java 的并发编程从「刀耕火种」进入「工业时代」。在它之前,程序员需要手动管理线程、担心死锁、性能调优;有了 J.U.C,这些问题变得可控多了。
2.3 Java 6 / Java 1.6(代号 Mustang,2006):稳步前进
Java 6 没有像 Java 5 那样的大规模语法升级,而是把重心放在了性能优化和企业级特性增强上。这一版属于「修修补补又一年」的类型,但每一处改进都很实用。
2.3.1 性能提升和 Web Services 增强
Java 6 在 JVM 层面做了大量优化,启动速度更快、垃圾回收更高效。Web Services 支持也得到了显著增强:
- JAX-WS 2.0:Web Services 的标准 API,支持 SOAP 协议
- JAXB 2.0:Java Architecture for XML Binding,XML 和 Java 对象互转
- WSIT(Web Services Interoperability Technologies):提升不同厂商 Web Services 之间的互操作性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // Java 6 的 Web Services 示例
// ⚠️ 提醒:javax.xml.ws 这套 Java EE 模块在 JDK 11 已被移除(JEP 320)。
// 所以这段代码在 JDK 6~10 上可以直接跑,在 JDK 11+ 上需要自己引入
// 相应的 Jakarta XML Web Services 依赖(包名也从 javax.* 变成了 jakarta.*)。
import javax.xml.ws.Endpoint;
public class SimpleWebService {
// 发布一个简单的 Web Service
public String sayHello(String name) {
return "Hello, " + name + "!";
}
public static void main(String[] args) {
// 一行代码发布 Web Service(Java 6 的便捷之处)
Endpoint.publish("http://localhost:8080/hello",
new SimpleWebService());
System.out.println("Web Service 已发布在 http://localhost:8080/hello?wsdl");
}
}
|
2.3.2 Java ME 和 Java EE 的分离
很多人以为"三个版本"是 Java 6 才有的,其实不正确——J2SE / J2EE / J2ME 这套划分从 1999 年就有了,Java 6 前后只是把名字统一改成了 Java SE / Java EE / Java ME。我们今天熟悉的三个方向是:
- Java SE(Standard Edition):标准版,桌面和服务器应用
- Java EE(Enterprise Edition):企业版,基于 SE 的企业级扩展
- Java ME(Micro Edition):微型版,用于手机和嵌入式设备
这种分离让各平台可以独立演进,减少了互相牵绊。Java ME 后来主要用在 IoT 设备上,而 Java EE 则成为企业级应用开发的事实标准。
2.3.3 Scripting 脚本语言支持
Java 6 引入了 Scripting API(javax.script),允许 Java 程序嵌入其他脚本语言,比如 JavaScript、Python(通过 Jython)、Ruby(通过 JRuby)等。
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
| import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.Invocable;
public class ScriptingDemo {
public static void main(String[] args) throws Exception {
ScriptEngineManager manager = new ScriptEngineManager();
// 获取 JavaScript 引擎:
// Java 6/7 内置的是 Rhino,Java 8 换成了 Nashorn。
// ⚠️ 但 Nashorn 在 Java 11 被标记废弃(JEP 335),
// 并在 Java 15 被彻底移除(JEP 372)。
// 所以这段代码在 JDK 15 及以上会找不到引擎,engine 会是 null。
// 今天要用脚本引擎,需要自己引入 GraalVM JS 等第三方实现。
ScriptEngine engine = manager.getEngineByName("JavaScript");
// 执行 JavaScript 代码
engine.eval("var message = 'Hello from JavaScript!';");
engine.eval("print(message)");
// 在 JavaScript 中定义函数,然后在 Java 中调用
engine.eval("function add(a, b) { return a + b; }");
Invocable invocable = (Invocable) engine;
Object result = invocable.invokeFunction("add", 10, 20);
System.out.println("JavaScript add(10, 20) = " + result);
}
}
|
这个特性让 Java 拥有了「动态脚本」能力,可以用来实现规则引擎、模板引擎,或者让非程序员编写业务逻辑。
2.4 Java 7(代号 Dolphin,2011):量变到质变
Java 7 是 Oracle 收购 Sun 后发布的第一个大版本。它带来了不少实用的新特性,虽然没有 Java 5 那样革命性,但每一项都让代码更简洁。
2.4.1 try-with-resources:不用手动关流,资源自动关闭
在 Java 7 之前,关闭资源需要写 finally 块,还容易遗漏:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // Java 7 之前的资源关闭(繁琐、易错)
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
Java 7 引入了 try-with-resources 语法,资源会自动关闭,代码简洁到哭:
1
2
3
4
5
6
7
8
| // Java 7+ 的 try-with-resources(优雅、安全)
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
}
// 不用写 finally,编译器帮你自动关闭资源
|
只要类实现了 AutoCloseable 接口(Closeable 的父接口),就可以用 try-with-resources。需要关闭多个资源?用分号隔开:
1
2
3
4
5
6
7
8
9
| // 同时管理多个资源
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
} // 两个资源都会自动关闭
|
2.4.2 switch 支持 String:再也不用 if-else 链判断字符串了
这是无数 Java 程序员翘首以盼的功能!
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
45
46
47
48
| // Java 7 之前:字符串判断只能用 if-else 链
String day = "MONDAY";
if (day.equals("MONDAY")) {
System.out.println("星期一");
} else if (day.equals("TUESDAY")) {
System.out.println("星期二");
} else if (day.equals("WEDNESDAY")) {
System.out.println("星期三");
} else if (day.equals("THURSDAY")) {
System.out.println("星期四");
} else if (day.equals("FRIDAY")) {
System.out.println("星期五");
} else if (day.equals("SATURDAY")) {
System.out.println("星期六");
} else if (day.equals("SUNDAY")) {
System.out.println("星期日");
} else {
System.out.println("未知日期");
}
// Java 7+:switch 原生支持 String(Java 7 时代只有冒号写法)
switch (day) {
case "MONDAY":
System.out.println("星期一");
break;
case "TUESDAY":
System.out.println("星期二");
break;
case "WEDNESDAY":
System.out.println("星期三");
break;
case "THURSDAY":
System.out.println("星期四");
break;
case "FRIDAY":
System.out.println("星期五");
break;
case "SATURDAY":
System.out.println("星期六");
break;
case "SUNDAY":
System.out.println("星期日");
break;
default:
System.out.println("未知日期");
break;
}
// 补充:Java 14+ 之后才有箭头写法 case "MONDAY" -> ...
|
switch 支持 String 后,代码可读性大幅提升,尤其是处理枚举、状态码、命令类型等场景。
2.4.3 钻石操作符(Diamond Operator):new ArrayList<>() 不用写两边类型
泛型类型推断让代码更简洁:
1
2
3
4
5
6
7
8
| // Java 7 之前:泛型类型要写两遍
Map<String, List<Map<String, Integer>>> complexMap =
new HashMap<String, List<Map<String, Integer>>>();
// 看这一行就眼晕……
// Java 7+:钻石操作符,编译器自动推断类型
Map<String, List<Map<String, Integer>>> complexMap = new HashMap<>();
// 简洁多了!
|
<> 因为长得像钻石,所以叫 Diamond Operator。编译器会根据左边的类型自动推断右边的泛型参数。
2.4.4 NIO.2 文件系统:更强大的文件操作 API
Java 7 引入了新的文件操作 API,提供了更强大的文件系统访问能力:
Path:表示文件路径,比 File 更灵活Files:文件操作工具类,复制、移动、读取、写入一条龙FileVisitor:遍历目录树WatchService:监控文件变化
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
| import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.io.IOException;
import java.util.List;
public class Nio2Demo {
public static void main(String[] args) throws IOException {
Path source = Paths.get("input.txt");
Path target = Paths.get("output.txt");
// 复制文件(一行代码搞定)
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
// 读取文件所有行
List<String> lines = Files.readAllLines(source);
for (String line : lines) {
System.out.println(line);
}
// 判断文件属性
System.out.println("文件大小: " + Files.size(source) + " 字节");
System.out.println("是否可读: " + Files.isReadable(source));
System.out.println("是否可执行: " + Files.isExecutable(source));
// 创建临时文件/目录
Path tempFile = Files.createTempFile("prefix", ".tmp");
Path tempDir = Files.createTempDirectory("dir");
System.out.println("临时文件: " + tempFile);
System.out.println("临时目录: " + tempDir);
}
}
|
NIO.2 的 API 比老的 File 类好用太多,终于可以愉快地操作文件和目录了!
2.4.5 二进制字面量:0b1010 或 0B1010
Java 7 开始支持二进制字面量,对底层编程、网络协议、位运算特别有用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // 十进制
int decimal = 42;
// 十六进制(之前就有)
int hex = 0x2A;
// 八进制(之前就有)
int octal = 052;
// 二进制字面量(Java 7 新增)
int binary = 0b101010;
// Java 7+ 还可以给下划线分隔(类似数学中的千位分隔符)
int bigNumber = 1_000_000; // 1000000
int binaryWithUnderscores = 0b1010_1010; // 170
int hexWithUnderscores = 0xDEAD_BEEF; // 注意:int 放不下这个值,实际存的是 -559038737
long bigHex = 0xDEAD_BEEFL; // 想要 3735928559 这个正数,得用 long + L 后缀
|
下划线分隔符可以让大数字更易读,但并不是"随便加":它不能出现在数字的开头或结尾,也不能紧挨着小数点、0x/0b 前缀或 L/f 这类后缀。比如 1_000_000、0b1010_1010 合法,而 _1000、1000_、0x_FF 都是错的。
2.4.6 异常处理改进:同一 catch 可以捕获多种异常
Java 7 之前,捕获多种异常需要写多个 catch 块:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| // Java 7 之前
try {
// 可能抛出多种异常的操作
} catch (IOException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// Java 7+:一个 catch 捕获多种异常(用 | 分隔)
try {
// 可能抛出多种异常的操作
} catch (IOException | SQLException | ClassNotFoundException e) {
// 注意:e 在这里实际上是 final 的,不能重新赋值
e.printStackTrace();
// 你可以通过 instanceof 判断具体类型
if (e instanceof IOException) {
System.out.println("IO 错误");
} else if (e instanceof SQLException) {
System.out.println("数据库错误");
}
}
|
这个特性叫 multi-catch,让异常处理代码更简洁。
2.4.7 Fork/Join 框架:并行计算的基础
Fork/Join 框架是 Java 7 引入的并行计算框架,专门用于「分而治之」类型的任务——把大任务拆成小任务,并行执行,最后合并结果。
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
45
46
47
48
49
50
51
| import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class ForkJoinDemo {
// 计算 1 到 n 的和,使用分治策略
static class SumTask extends RecursiveTask<Long> {
private final long from;
private final long to;
private static final long THRESHOLD = 10_000; // 阈值
SumTask(long from, long to) {
this.from = from;
this.to = to;
}
@Override
protected Long compute() {
// 如果范围足够小,直接计算
if (to - from <= THRESHOLD) {
long sum = 0;
for (long i = from; i <= to; i++) {
sum += i;
}
return sum;
}
// 拆分任务
long mid = (from + to) / 2;
SumTask left = new SumTask(from, mid);
SumTask right = new SumTask(mid + 1, to);
// 叉出子任务,然后合并结果
left.fork();
right.fork();
return left.join() + right.join();
}
}
public static void main(String[] args) {
ForkJoinPool pool = ForkJoinPool.commonPool();
long n = 1_000_000;
long start = System.currentTimeMillis();
long result = pool.invoke(new SumTask(1, n));
long end = System.currentTimeMillis();
System.out.println("计算 1 到 " + n + " 的和");
System.out.println("结果: " + result);
System.out.println("耗时: " + (end - start) + " ms");
}
}
|
Fork/Join 是后来 Java 8 Stream 并行流和 CompletableFuture 的底层基础。
2.5 Java 8(代号 Spider,2014):现代化 Java 的起点——最重要版本
「Java 8 之前和 Java 8 之后,是两种完全不同的编程体验。」——不知道是谁说的,但每个 Java 程序员都会认同。
Java 8 是 Oracle 收购 Sun 后真正意义上的「现代化 Java」。它引入了 Lambda 表达式、Stream API、Optional 类、新的日期时间 API……这些特性彻底改变了 Java 的编程风格,让 Java 从一门「老派」语言,华丽转身为「现代」语言。
Java 8 是 Java 历史上最重要的版本,没有之一。即使到了 2026 年,很多公司的生产环境依然跑在 Java 8 上。
2.5.1 Lambda 表达式:匿名内部类的革命,代码从 10 行变成 1 行
Lambda 表达式是 Java 8 最核心的特性,它让函数式编程成为可能。在 Lambda 之前,Java 只有「面向对象」一条路;有了 Lambda,「函数」也可以作为一等公民了。
1
2
3
4
5
6
7
8
9
10
11
12
13
| // Java 7 的匿名内部类(啰嗦、冗长)
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello, Lambda!");
}
};
// Java 8 的 Lambda 表达式(简洁、优雅)
Runnable r2 = () -> System.out.println("Hello, Lambda!");
// 执行
r2.run();
|
Lambda 的语法规则:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // 完整语法:(参数列表) -> { 方法体 }
// 1. 参数类型可省略(编译器推断)
// 2. 单个参数时,括号可省略
// 3. 单行方法体时,return 和大括号可省略(隐含 return)
// 示例:各种 Lambda 形式
Runnable r1 = () -> System.out.println("无参数");
Consumer<String> c1 = (String s) -> System.out.println(s); // 完整形式
Consumer<String> c2 = s -> System.out.println(s); // 类型推断
Consumer<String> c3 = System.out::println; // 方法引用
// 多参数
Comparator<String> cmp = (s1, s2) -> s1.length() - s2.length();
// 有方法体
Predicate<Integer> p = n -> {
if (n > 0) return true;
return false;
};
// 带返回
Function<String, Integer> f = s -> s.length();
|
2.5.2 Stream API:集合操作的新方式,链式调用风靡编程界
Stream API 是 Java 8 的另一大杀器。它让你可以用「流」的方式处理集合——链式调用、函数式风格、一行代码搞定过滤/映射/排序/统计。
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
45
46
47
48
| import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamDemo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 传统方式:过滤偶数,平方后求和
int sum = 0;
for (Integer n : numbers) {
if (n % 2 == 0) {
sum += n * n;
}
}
System.out.println("传统方式: " + sum);
// Stream 方式:一行搞定
int streamSum = numbers.stream()
.filter(n -> n % 2 == 0) // 过滤偶数
.mapToInt(n -> n * n) // 平方
.sum(); // 求和
System.out.println("Stream 方式: " + streamSum);
// 更多示例
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// 找出长度大于 4 的名字,转大写,排序
List<String> result = names.stream()
.filter(name -> name.length() > 4)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
System.out.println("结果: " + result); // [ALICE, CHARLIE, DAVID]
// 统计
long count = names.stream().count();
String longest = names.stream().max((a, b) -> a.length() - b.length()).orElse("");
System.out.println("最长名字: " + longest);
// 并行流(充分利用多核)
long parallelSum = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.mapToInt(n -> n * n)
.sum();
System.out.println("并行流结果: " + parallelSum);
}
}
|
Stream 的操作分为中间操作(返回 Stream)和终端操作(返回结果)。常见的中间操作有 filter、map、flatMap、distinct、sorted、limit、skip;终端操作有 forEach、collect、count、sum、reduce、min/max/findFirst/findAny。
2.5.3 Optional 类:消灭 NullPointerException 的希望
NullPointerException 是 Java 程序员最常遇到的异常,江湖人称「十亿美金的错误」。Optional 提供了一种更优雅的空值处理方式。
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
| import java.util.Optional;
public class OptionalDemo {
public static void main(String[] args) {
// 创建 Optional
Optional<String> empty = Optional.empty();
Optional<String> name = Optional.of("Alice"); // 不能传 null
Optional<String> nullable = Optional.ofNullable(null); // 可以传 null
// 安全地获取值
String value = nullable.orElse("默认值"); // 为空就返回默认值
String value2 = nullable.orElseGet(() -> "计算出来的默认值"); // 延迟计算版本
// orElseThrow:为空就抛异常。注意 nullable 本身是空的,
// 如果写成 nullable.orElseThrow(...) 这一行会立刻抛异常、程序直接中断,
// 所以这里换成有值的 name 来演示:
String value3 = name.orElseThrow(() -> new RuntimeException("不能为空!"));
System.out.println("value / value2 / value3 = " + value + " / " + value2 + " / " + value3);
// ifPresent:值存在时执行
name.ifPresent(n -> System.out.println("名字是: " + n));
// map 转换
Optional<Integer> length = name.map(String::length);
System.out.println("名字长度: " + length.orElse(0));
// flatMap 链式调用
Optional<String> result = name
.map(String::toUpperCase)
.filter(s -> s.length() > 3)
.flatMap(s -> Optional.of("Hello, " + s));
System.out.println(result.orElse("没结果"));
// 链式操作示例:安全地获取用户所在城市名称
String city = getUser()
.flatMap(User::getAddress)
.flatMap(Address::getCity)
.map(City::getName)
.orElse("未知城市");
// 传统方式需要多层判断
// if (user != null) {
// if (user.getAddress() != null) {
// if (user.getAddress().getCity() != null) {
// city = user.getAddress().getCity().getName();
// }
// }
// }
}
// 示例方法
static Optional<User> getUser() {
return Optional.of(new User(new Address(new City("北京"))));
}
// 内部类示例
static class User {
private Address address;
User(Address address) { this.address = address; }
Optional<Address> getAddress() { return Optional.ofNullable(address); }
}
static class Address {
private City city;
Address(City city) { this.city = city; }
Optional<City> getCity() { return Optional.ofNullable(city); }
}
static class City {
private String name;
City(String name) { this.name = name; }
String getName() { return name; }
}
}
|
Optional 的核心思想是:「让空值检查变得可视化、可链式操作」。虽然 Optional 不能完全消灭 NPE,但它让代码更清晰、更安全。
2.5.4 接口的默认方法(Default Methods):接口可以写实现了
Java 8 之前,接口只能声明方法,不能写实现。Java 8 引入了 default 方法,让接口可以包含具体实现。
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
| // Java 8 之前的接口(这里叫 AnimalOld,只是为了不和下面的接口重名)
interface AnimalOld {
void speak(); // 抽象方法,必须实现
}
// Java 8+ 的接口可以有默认方法
interface Animal {
void speak(); // 抽象方法
default void breathe() { // 默认方法,有默认实现
System.out.println("呼吸中...");
}
default void sleep() {
System.out.println("睡着了zzZ");
}
}
// 实现类只需要关注自己特有的行为
class Dog implements Animal {
@Override
public void speak() {
System.out.println("汪汪汪!");
}
// breathe() 和 sleep() 可以直接使用默认实现
// 也可以重写默认方法
@Override
public void sleep() {
System.out.println("狗趴着睡...");
}
}
// 默认方法的好处:
// 1. 向后兼容:给现有接口添加方法不用改所有实现类
// 2. 多继承:类可以实现多个接口,每个接口可以有默认方法
// 3. 代码复用:减少重复代码
|
小知识:Java 8 的 List 接口添加了 sort() 默认方法、Stream 的 forEachOrdered() 等,这就是为什么Collections.sort 可以直接用 list.sort() 代替。
Java 8 之前,处理日期时间是个噩梦:Date 类可变且坑多,SimpleDateFormat 线程不安全,月份从 0 开始……
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.time.Period;
import java.time.Duration;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
public class DateTimeDemo {
public static void main(String[] args) {
// LocalDate:日期(年-月-日)
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(1990, 1, 15); // 直接用数字,不用减 1
System.out.println("今天: " + today);
System.out.println("生日: " + birthday);
// LocalTime:时间(时:分:秒)
LocalTime now = LocalTime.now();
System.out.println("现在时间: " + now);
// LocalDateTime:日期+时间
LocalDateTime meeting = LocalDateTime.of(2026, 3, 30, 14, 30);
System.out.println("会议时间: " + meeting);
// ZonedDateTime:带时区的日期时间
ZonedDateTime tokyoTime = ZonedDateTime.now(java.time.ZoneId.of("Asia/Tokyo"));
System.out.println("东京时间: " + tokyoTime);
// 日期计算(链式调用,爽!)
LocalDate nextWeek = today.plusWeeks(1);
LocalDate nextMonth = today.plusMonths(1);
LocalDate lastYear = today.minusYears(1);
System.out.println("下周: " + nextWeek);
// Period:日期间隔
Period age = Period.between(birthday, today);
System.out.println("年龄: " + age.getYears() + " 岁");
// Duration:时间间隔
Duration twoHours = Duration.ofHours(2);
LocalTime endTime = now.plus(twoHours);
System.out.println("两小时后: " + endTime);
// ChronoUnit:更方便的日期时间计算
long daysBetween = ChronoUnit.DAYS.between(birthday, today);
System.out.println("活了 " + daysBetween + " 天");
// 格式化(线程安全!终于!)
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String formatted = meeting.format(fmt);
System.out.println("格式化: " + formatted);
// 解析
LocalDate parsed = LocalDate.parse("2026-03-30");
System.out.println("解析: " + parsed);
}
}
|
java.time API 的设计非常出色:类是不可变的(线程安全)、API 清晰直观、时区和格式化都处理得很好。唯一的问题是——迁移老代码成本较高,很多人还是习惯性地用 Date。
2.5.6 方法引用(Method Reference):String::valueOf 的优雅
方法引用是 Lambda 表达式的简写形式。当 Lambda 体只是调用某个方法时,可以用方法引用替代。
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
45
46
47
| import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class MethodReferenceDemo {
public static void main(String[] args) {
// 方法引用一共四种形式(注意第 2、3 种很容易被混为一谈):
// 1. 静态方法引用:ClassName::staticMethod
Function<Integer, String> f1 = String::valueOf; // 等价于 n -> String.valueOf(n)
System.out.println(f1.apply(42)); // "42"
// 2. 未绑定实例方法引用:ClassName::instanceMethod
// 接收者作为"第一个参数"传进来
Function<String, Integer> f2 = String::length; // 等价于 s -> s.length()
System.out.println(f2.apply("World")); // 5
// 3. 绑定实例方法引用:instance::instanceMethod
// 接收者已经固定,剩下的才是方法参数
Consumer<String> c1 = System.out::println; // 等价于 s -> System.out.println(s)
c1.accept("Hello, Method Reference!");
String str = "Hello";
Supplier<Integer> s0 = str::length; // 等价于 () -> str.length()
System.out.println(s0.get()); // 5
// 4. 构造方法引用:ClassName::new
Supplier<List<String>> s1 = ArrayList::new; // 等价于 () -> new ArrayList<>()
System.out.println(s1.get().size()); // 0
// 实际应用:结合 Stream 使用
List<String> names = Arrays.asList("alice", "bob", "charlie");
// 全部转成大写
names.stream()
.map(String::toUpperCase) // 等价于 s -> s.toUpperCase()
.forEach(System.out::println);
// 排序(使用 Comparable 的方法引用)
names.stream()
.sorted(String::compareToIgnoreCase)
.forEach(System.out::println);
}
}
|
方法引用让代码更简洁、更易读,尤其在 Stream 操作中非常实用。
2.5.7 CompletableFuture:异步编程的利器
Java 8 的 CompletableFuture 是在 Future 基础上封装的异步编程利器,支持流式调用、回调、组合等操作。
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
| import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 模拟耗时操作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello, CompletableFuture!";
});
// 注册回调(不阻塞)
future.thenAccept(result -> System.out.println("收到结果: " + result));
// 链式调用:先查用户,再查订单,最后发邮件
CompletableFuture<Void> chain = CompletableFuture
.supplyAsync(() -> "user123") // 异步获取用户ID
.thenCompose(userId -> // 扁平化,接收一个新的 CompletableFuture
CompletableFuture.supplyAsync(() -> getOrderByUserId(userId)))
.thenCompose(order -> // 再查订单详情
CompletableFuture.supplyAsync(() -> getOrderDetails(order)))
.thenAccept(details -> // 最后发送邮件
System.out.println("发送邮件: " + details));
chain.join(); // 等待完成
// 异常处理
CompletableFuture<String> withError = CompletableFuture
.supplyAsync(() -> {
if (true) throw new RuntimeException("出错了!");
return "result";
})
.exceptionally(ex -> {
System.err.println("捕获异常: " + ex.getMessage());
return "默认值";
})
.thenApply(result -> result + " - 处理后");
System.out.println("异常处理结果: " + withError.get());
// 组合多个 Future
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "A");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "B");
CompletableFuture<String> combined = CompletableFuture
.allOf(future1, future2) // 等待所有完成
.thenApply(v -> future1.join() + future2.join());
System.out.println("组合结果: " + combined.get()); // "AB"
}
static String getOrderByUserId(String userId) {
return "Order-" + userId;
}
static String getOrderDetails(String orderId) {
return "Details of " + orderId;
}
}
|
CompletableFuture 让异步编程变得优雅、可读、可组合,是微服务架构中处理并发请求的核心工具。
Java 8 之前,JVM 使用 PermGen(Permanent Generation)来存储类的元数据(类名、方法名、字段名等)。这带来了一个大问题:java.lang.OutOfMemoryError: PermGen space——只要你加载的类足够多(比如用大量框架、动态代理、字节码生成),PermGen 就会爆掉。
Java 8 用 Metaspace 取代了 PermGen:
- Metaspace 使用本地内存(Native Memory),而不是 JVM 堆内存
- Metaspace 默认可以动态扩展,只受限于物理内存和操作系统限制
- 不会再出现 PermGen space 的 OOM
1
2
3
4
5
6
7
8
| // JVM 参数对比
// Java 7 及之前:需要手动设置 PermGen 大小
// -XX:PermSize=256m -XX:MaxPermSize=512m
// Java 8+:Metaspace 自动管理,一般不需要设置
// 如果想限制 Metaspace 大小(可选):
// -XX:MaxMetaspaceSize=256m
// -XX:MetaspaceSize=128m
|
这个改动对开发者来说是重大利好——少了一个需要调的参数,少了一类噩梦般的 OOM。
2.6 Java 9(2017,模块化项目 Project Jigsaw):模块化时代
Java 9 发布了!等了好久的模块化系统终于来了。三年磨一剑,Java 9 带来的最大变化就是 Jigsaw 模块化系统(Jigsaw 是项目名,并不是版本的发布代号)。
2.6.1 模块化系统(Jigsaw):Java 自己的模块化管理
模块化是 Java 9 最核心的特性。它的目标很宏大:让 Java 应用可以模块化,告别「一整包 JAR」式的依赖管理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // module-info.java
// 这是一个模块的声明文件,放在模块根目录下
module com.myapp {
// 导出(exports):这个模块向其他模块开放的包
exports com.myapp.api;
exports com.myapp.service;
// 依赖(requires):这个模块依赖的其他模块
requires com.fasterxml.jackson.core; // 需要 Jackson
requires java.sql; // 需要 JDBC
// 传递性导出(exports ... to)
// 只有指定的模块才能访问这个包
exports com.myapp.internal to com.myapp.impl;
// 服务提供(provides ... with):提供服务实现
provides com.myapp.spi.Logger with com.myapp.impl.FileLogger;
// 服务使用(uses):依赖某个服务接口
uses com.myapp.spi.Logger;
}
|
模块化的好处:
- 显式依赖:每个模块声明自己需要什么,不用再靠猜测和文档
- 封装:内部实现可以隐藏起来,只暴露必要的 API
- 可配置性:可以选择性加载模块,构建更小、更安全的运行时(JLink 可以打包出裁剪过的 JRE)
- 安全:限制模块间的访问,减少攻击面
1
2
3
| # 使用 jlink 创建自定义运行时
jlink --add-modules java.base,java.sql,com.myapp --output my-runtime
# 生成了一个只包含必要模块的小型 JRE
|
2.6.2 JShell:交互式编程,像 Python 一样玩 Java(REPL)
Python 程序员可以输入一行代码立即看到结果,Java 程序员只能写完类、编译、运行……JShell 让 Java 也有了 REPL(Read-Eval-Print Loop)!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # 启动 JShell
$ jshell
| 欢迎使用 JShell — 您的代码片段在此处生效。
| 要获得相关信息,请键入 /help
jshell> int x = 10
x ==> 10
jshell> int y = 20
y ==> 20
jshell> x + y
$3 ==> 30
jshell> String greeting(String name) {
...> return "Hello, " + name + "!";
...> }
| 已创建 方法 greeting(String)
jshell> greeting("Java")
$5 ==> "Hello, Java!"
jshell> /exit
| 再见
|
JShell 特别适合:快速测试 API、验证算法思路、调试代码片段、学习 Java 新特性。不用再为了「试试这个方法怎么用」而创建一个新项目。
2.6.3 接口私有方法:接口内部也可以写私有工具方法了
Java 9 允许接口包含 private 方法,这些方法可以作为内部工具方法,被 default 方法调用:
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
| public interface StringProcessor {
default String process(String input) {
// 调用私有工具方法
String cleaned = cleanInput(input);
String normalized = normalize(cleaned);
return applyTransform(normalized);
}
// 私有方法:接口内部的实现细节
private String cleanInput(String input) {
if (input == null) {
return "";
}
return input.trim().toLowerCase();
}
private String normalize(String input) {
// 规范化处理逻辑
return input.replaceAll("\\s+", " ");
}
private String applyTransform(String input) {
// 子类可以覆盖 process 方法,但无法调用这些私有方法
return "[" + input + "]";
}
}
// 使用
class UpperCaseProcessor implements StringProcessor {
// process 是默认方法,子类不实现也能直接用;
// 接口里的 private 方法只在接口内部可见,子类既看不到也调不到
}
class InterfacePrivateDemo {
public static void main(String[] args) {
StringProcessor processor = new UpperCaseProcessor();
System.out.println(processor.process(" Hello WORLD "));
// 输出: [hello world]
}
}
|
这个特性解决了接口中 default 方法代码重复的问题,让接口可以包含更复杂的默认实现逻辑。
2.6.4 集合工厂方法:List.of()、Set.of()、Map.of(),一行创建不可变集合
Java 9 为集合接口添加了静态工厂方法,可以一行代码创建不可变集合:
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
| import java.util.List;
import java.util.Set;
import java.util.Map;
public class CollectionFactoryDemo {
public static void main(String[] args) {
// 不可变 List
List<String> immutableList = List.of("Apple", "Banana", "Cherry");
// immutableList.add("Date"); // UnsupportedOperationException!
// 不可变 Set
Set<Integer> immutableSet = Set.of(1, 2, 3, 4, 5);
// 不可变 Map
Map<String, Integer> immutableMap = Map.of(
"Alice", 25,
"Bob", 30,
"Charlie", 35
);
// Map 还有另一种方式:entries 方式
Map<String, Integer> anotherMap = Map.ofEntries(
Map.entry("David", 40),
Map.entry("Eve", 45)
);
// 遍历
System.out.println("List: " + immutableList);
System.out.println("Set: " + immutableSet);
System.out.println("Map: " + immutableMap);
// Map.getOrDefault
int unknownAge = immutableMap.getOrDefault("Frank", -1); // -1,不存在的 key
System.out.println("Frank's age: " + unknownAge);
}
}
|
注意:这些集合是不可变的,但内部元素如果是可变对象(比如 List<StringBuilder>),元素本身还是可以修改的。
2.6.5 Stream 新增方法:takeWhile()、dropWhile()、iterate() 重载
Java 9 为 Stream 添加了几个实用的新方法:
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
| import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.util.List;
public class StreamEnhancementDemo {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// takeWhile:从头开始取,满足条件就停止(遇到不满足的立即停止)
List<Integer> taken = numbers.stream()
.takeWhile(n -> n < 5)
.collect(Collectors.toList()); // 注意:Stream.toList() 是 Java 16 才有的,
// Java 9 时代要用 collect(Collectors.toList())
System.out.println("takeWhile(< 5): " + taken); // [1, 2, 3, 4]
// 注意 takeWhile 是"遇到第一个不满足就立刻收工":
// List.of(3, 1, 4, 1, 5, 9, 2, 6) 用 takeWhile(n -> n < 5) 得到的是 [3, 1, 4, 1],
// 后面的 2 虽然也小于 5,但因为在 5 之后,已经被跳过了(这和 filter 完全不同)
// dropWhile:从头开始丢弃,满足条件的都丢掉,遇到第一个不满足的就开始保留
List<Integer> dropped = numbers.stream()
.dropWhile(n -> n < 5)
.collect(Collectors.toList());
System.out.println("dropWhile(< 5): " + dropped); // [5, 6, 7, 8, 9, 10]
// iterate 重载:增加终止条件
// Java 8 的 Stream.iterate(start, unaryOperator) 是无限的
// Java 9 的 iterate(start, predicate, unaryOperator) 有终止条件
List<Integer> fibonacci = Stream.iterate(
new long[]{0, 1}, // 起始值
t -> t[0] < 1000, // 终止条件:作用于"当前元素"
t -> new long[]{t[1], t[0] + t[1]} // 迭代函数
).map(t -> (int) t[0]).collect(Collectors.toList());
System.out.println("Fibonacci < 1000: " + fibonacci);
// ofNullable:处理可能为 null 的元素
String nullable = null;
List<String> result = Stream.ofNullable(nullable)
.collect(Collectors.toList());
System.out.println("ofNullable(null): " + result); // [],空流
}
}
|
2.6.6 HTTP Client 标准化:java.net.http 模块
Java 9 把新的 HTTP Client 作为孵化模块(jdk.incubator.http)引入,到 Java 11 才正式标准化为 java.net.http 包,支持 HTTP/2 和 WebSocket(因此下面这段代码请按 Java 11+ 来理解):
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
| import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class HttpClientDemo {
public static void main(String[] args) throws Exception {
// 创建 HTTP Client
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // 默认 HTTP/2,自动降级
.connectTimeout(java.time.Duration.ofSeconds(10))
.build();
// 发送 GET 请求
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.github.com/users"))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("状态码: " + response.statusCode());
System.out.println("响应体: " + response.body().substring(0, 200) + "...");
// 异步发送请求
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenAccept(resp -> System.out.println("异步收到: " + resp.statusCode()))
.join();
// POST 请求
HttpRequest postRequest = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/post"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"name\":\"Java\"}"))
.build();
HttpResponse<String> postResponse = client.send(postRequest,
HttpResponse.BodyHandlers.ofString());
System.out.println("POST 响应: " + postResponse.statusCode());
}
}
|
之前,Java 只能用 HttpURLConnection 或者第三方库(Apache HttpClient、OkHttp)来发送 HTTP 请求。现在有了标准 API,简单多了。
2.7 Java 10~11(2018 年 3 月 ~ 2018 年 9 月):快速迭代
Oracle 改变了 Java 的发布节奏——从每两年一个大版本,变成每六个月一个小版本。Java 10 和 Java 11 就是新节奏下的第一批产物。
2.7.1 var 局部变量类型推断:写 var 而不是 String,编译器帮你推断
Java 10 引入了局部变量类型推断(var 关键字),让你在声明局部变量时可以省略类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
| // 之前的写法
String name = "Alice";
Map<String, List<Integer>> complex = new HashMap<String, List<Integer>>();
Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
// var 写法:编译器自动推断类型
var name = "Alice"; // String
var complex = new HashMap<String, List<Integer>>(); // HashMap<String, List<Integer>>
var iterator = map.entrySet().iterator(); // Iterator<Map.Entry<String, Object>>
// var 不是关键字,是保留类型名
// 所以 var 可以作为变量名,但建议不要
int var = 42; // 合法,但不推荐
|
注意:var 只能用于局部变量(方法内、代码块内),不能用于字段、方法参数、返回值。var 不是动态类型,是编译时类型推断。
2.7.2 字符串增强:isBlank()、lines()、strip()、repeat()
Java 11 给 String 类添加了一堆实用方法:
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
| public class StringEnhancementDemo {
public static void main(String[] args) {
String str = " Hello\nWorld ";
// isBlank():判断是否为空或只包含空白字符
System.out.println(" ".isBlank()); // true(Java 11+)
// strip():去除首尾空白(Unicode 感知,比 trim() 更智能)
System.out.println(" hello ".strip()); // "hello"(trim 只处理 ASCII)
System.out.println("\u2002hello\u2002".strip()); // "hello"(trim 不处理这个)
// lines():按行分割,返回 Stream
String multiline = "line1\nline2\nline3";
long lineCount = multiline.lines().count();
System.out.println("行数: " + lineCount);
// repeat():重复字符串
System.out.println("Ha".repeat(5)); // "HaHaHaHaHa"
// ⚠️ 版本提醒:下面这两个方法不属于 Java 11。
// stripIndent()、translateEscapes() 和文本块 """...""" 都是 Java 13 预览、
// Java 15 才正式转正的特性,在 Java 11 上写完会直接编译失败。
// (Java 15+)文本块 + stripIndent():移除每行公共的前导缩进
String indented = """
Hello
World
""";
System.out.println(indented.stripIndent());
// (Java 15+)translateEscapes():把字面量里的转义序列真正转换出来
String escaped = "Hello\\nWorld".translateEscapes();
System.out.println(escaped); // Hello(换行)World
}
}
|
2.7.3 文件操作增强:readString()、writeString() 一行搞定
Java 11 给 Files 类添加了直接读写 String 的方法:
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
| import java.nio.file.Files;
import java.nio.file.Path;
public class FilesStringDemo {
public static void main(String[] args) throws Exception {
Path tempFile = Files.createTempFile("test", ".txt");
// 一行代码写文件
Files.writeString(tempFile, "Hello, Java 11!");
// 一行代码读文件
String content = Files.readString(tempFile);
System.out.println("文件内容: " + content);
// 追加内容
Files.writeString(tempFile, "\n新一行", java.nio.file.StandardOpenOption.APPEND);
// 读取所有行
var lines = Files.readAllLines(tempFile);
System.out.println("行数: " + lines.size());
// 清理
Files.deleteIfExists(tempFile);
}
}
|
之前要读写文本文件需要用 BufferedReader/BufferedWriter,现在一行搞定。
2.7.4 集合 toArray(IntFunction):更方便的集合转数组
Java 11 为集合添加了 toArray(IntFunction) 方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| import java.util.List;
import java.util.ArrayList;
public class CollectionToArrayDemo {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
// Java 11 之前:只能传一个现成的数组(数组太小会被自动替换成新数组)
String[] arr1 = names.toArray(new String[0]);
// Java 11 新增:可以传一个 IntFunction,由集合自己决定数组大小
String[] arr2 = names.toArray(length -> new String[length]);
// 最常见的写法就是构造方法引用,效果和上面完全一样
String[] arr3 = names.toArray(String[]::new);
System.out.println("数组长度: " + arr3.length);
for (String name : arr3) {
System.out.println(name);
}
}
}
|
2.7.5 局部变量 Lambda 语法:var 可以用在 Lambda 参数上了
Java 11 允许在 Lambda 参数上使用 var:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| import java.util.function.Function;
public class LambdaVarDemo {
public static void main(String[] args) {
// 之前的 Lambda 参数
Function<String, String> f1 = (String s) -> s.toUpperCase();
Function<String, String> f2 = s -> s.toUpperCase();
// Java 11+:可以用 var(给参数加注解时必须用 var)
Function<String, String> f3 = (@Nonnull var s) -> s.toUpperCase();
// 有什么用?可以在参数上加注解!
System.out.println(f3.apply("hello"));
}
// 自定义注解
@interface Nonnull {}
}
|
这个特性主要是为了给 Lambda 参数加注解。比如你想标记某个参数为 @Nullable 或 @Nonnull,就必须用 var。
2.7.6 ZGC:低延迟垃圾收集器(实验特性)
Java 11 引入了 ZGC(Z Garbage Collector)作为实验特性。ZGC 的目标是:停顿时间不超过 10ms,且停顿时间不随堆大小增加而增加。
1
2
3
4
5
| # 启用 ZGC(Java 11 实验,Java 15+ 正式支持)
java -XX:+UseZGC -Xmx8g MyApp
# Java 21+ 推荐配置
java -XX:+UseZGC -Xmx64g MyApp
|
ZGC 非常适合大内存(64GB+)低延迟的应用,比如金融交易、游戏服务器、实时数据处理等。
2.7.7 Java 11 是 LTS 版本,很多公司开始从 8 迁移到 11
Java 11 是继 Java 8 之后的第一个 LTS(Long-Term Support) 版本。Oracle 的新策略是:
- LTS 版本:每两年发布一次,持续支持时间远长于普通版本(以 Java 21 为例,Oracle JDK 的高级支持到 2028 年 9 月,延长支持到 2031 年 9 月;不同厂商的支持周期略有差异)
- 非 LTS 版本:每六个月一个,支持六个月
很多企业发现 Java 8 的安全漏洞越来越多,而 Java 11 不但性能更好,还有长期支持,于是开始了大规模的版本迁移。Java 11 成了「新一代 Java 8」。
2.8 Java 1216(20192021):特性爆发
从 Java 12(2019 年 3 月)到 Java 16(2021 年 3 月)这两年,是 Java 新特性最密集的爆发期。Oracle 采用「预览特性」机制——新功能先以预览版发布,收集反馈后再决定是否保留。这种方式让 Java 既能快速迭代,又能保持稳定性。
2.8.1 Switch 表达式预览(Java 12)→ 正式版(Java 14)
Java 12 引入了新的 Switch 表达式,Java 14 正式发布。
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
| // 传统 switch 语句(Java 14 前)
String dayType;
switch (day) {
case "MONDAY":
case "TUESDAY":
case "WEDNESDAY":
case "THURSDAY":
case "FRIDAY":
dayType = "工作日";
break;
case "SATURDAY":
case "SUNDAY":
dayType = "周末";
break;
default:
dayType = "未知";
}
// Java 14+ 的 switch 表达式(箭头语法)
String dayType = switch (day) {
case "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" -> "工作日";
case "SATURDAY", "SUNDAY" -> "周末";
default -> "未知";
};
// switch 表达式可以有返回值
int days = switch (month) {
case "JAN", "MAR", "MAY", "JUL", "AUG", "OCT", "DEC" -> 31;
case "APR", "JUN", "SEP", "NOV" -> 30;
case "FEB" -> 28; // 忽略闰年简化示例
default -> 0;
};
|
新 switch 的优势:箭头语法不会再漏写 break、一个 case 可以用逗号分隔多个值、switch 可以直接作为表达式返回值。
2.8.2 文本块(Text Blocks)预览(Java 13)→ 正式版(Java 15)
文本块让你可以写多行字符串,而不用拼接和转义:
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
| // 传统方式:字符串拼接和转义
String json = "{\n" +
" \"name\": \"Alice\",\n" +
" \"age\": 25\n" +
"}";
// Java 15+ 文本块
String json = """
{
"name": "Alice",
"age": 25
}
""";
// HTML 示例
String html = """
<html>
<body>
<h1>Hello, Text Blocks!</h1>
</body>
</html>
""";
// SQL 示例
String sql = """
SELECT id, name, email
FROM users
WHERE age > 18
ORDER BY name
""";
|
文本块会自动处理缩进,""" 后面的内容会作为字符串的起始位置,不需要手动去对齐。
2.8.3 record 记录类预览(Java 14)→ 正式版(Java 16)
record 是 Java 16 正式发布的「数据传输对象(DTO)」的简洁写法:
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
45
46
| import java.util.Objects;
public class RecordDemo {
// 传统方式:定义一个 Point 类,代码量惊人
public static class PointClass {
private final double x;
private final double y;
public PointClass(double x, double y) {
this.x = x;
this.y = y;
}
public double x() { return x; }
public double y() { return y; }
@Override
public boolean equals(Object o) {
if (!(o instanceof PointClass)) return false;
PointClass p = (PointClass) o;
return Double.compare(p.x, x) == 0 && Double.compare(p.y, y) == 0;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return "Point[x=" + x + ", y=" + y + "]";
}
}
// record 方式:一行代码搞定!
// 它自动生成:构造函数、取值方法 x()/y()、equals、hashCode、toString
// record 是不可变的(所有字段都是 final)
public record Point(double x, double y) {}
public static void main(String[] args) {
Point p = new Point(3.0, 4.0);
System.out.println(p.x()); // 3.0
System.out.println(p.y()); // 4.0
System.out.println(p); // Point[x=3.0, y=4.0]
}
}
|
record 特别适合:数据传输对象(DTO)、元组返回值、配置对象、日志记录等场景。
注意:record 的构造函数可以做参数验证:
1
2
3
4
5
6
7
8
9
10
11
| public record Person(String name, int age) {
// compact(紧凑)构造函数:不用重复写参数列表和赋值语句
public Person {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
// 给参数重新赋值,就会改变最终写入字段的值,例如:
// name = name.trim();
// 这算是"规范化"参数,不是直接改字段(record 的字段始终是 final)
}
}
|
2.8.4 Pattern Matching for instanceof 预览(Java 14)→ 正式版(Java 16)
以前 instanceof 后需要强制类型转换:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // Java 14 前
if (obj instanceof String) {
String s = (String) obj; // 啰嗦,还要强制转换
System.out.println(s.length());
}
// Java 16+ Pattern Matching for instanceof
if (obj instanceof String s) { // 一步到位
System.out.println(s.length()); // s 直接可用,不用强制转换
}
// 可以组合条件
if (obj instanceof String s && s.length() > 5) {
System.out.println("长字符串: " + s);
}
|
这个特性让 instanceof 检查和类型转换一步完成,代码更简洁。
2.8.5 密封类(Sealed Classes)预览(Java 15)→ 正式版(Java 17)
密封类限制哪些类可以继承它,实现更精细的继承控制:
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
| // 密封类:只有 permit 的类才能继承
public sealed class Shape
permits Circle, Rectangle, Triangle {
}
// Circle 可以继续被继承
public final class Circle extends Shape { // final:不能再被继承
private double radius;
public Circle(double radius) { this.radius = radius; }
}
// Rectangle 也可以是 sealed,但 sealed 类必须写明 permits 列表
public sealed class Rectangle extends Shape permits Square {
private double width, height;
public Rectangle(double w, double h) { this.width = w; this.height = h; }
}
// Square 继承 Rectangle,并标记为 final
public final class Square extends Rectangle {
public Square(double side) { super(side, side); }
}
// Triangle 是 non-sealed,可以被任何类继承
public non-sealed class Triangle extends Shape {
private double a, b, c;
public Triangle(double a, double b, double c) { this.a = a; this.b = b; this.c = c; }
}
|
密封类的 permits 列表让编译器知道「只有这些类可以继承我」,这对于模式匹配特别重要——编译器可以检查所有可能的子类,确保 switch 表达式处理了每一种情况。
2.8.6 jpackage 打包工具(Java 14):打包成原生安装程序
Java 14 引入的 jpackage 可以把 Java 应用打包成原生安装程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 打包成 Windows MSI 安装程序
jpackage --name MyApp \
--input target/ \
--main-jar myapp.jar \
--type msi \
--win-console
# 打包成 macOS DMG
jpackage --name MyApp \
--input target/ \
--main-jar myapp.jar \
--type dmg
# 打包成 Linux deb/rpm
jpackage --name MyApp \
--input target/ \
--main-jar myapp.jar \
--type deb
|
jpackage 让 Java 应用的分发变得简单——不用再让用户手动安装 JRE,直接分发一个安装包就行。
2.8.7 移除 Nashorn JavaScript 引擎
Java 11 废弃了 Nashorn,Java 15 正式移除。Nashorn 是 Java 8 引入的 JavaScript 引擎,让 Java 可以执行 JavaScript 代码。随着 WebAssembly 的发展和 GraalJS 的成熟,Nashorn 显得有些鸡肋,Oracle 决定让它退出历史舞台。
2.9 Java 17(LTS,2021):成熟稳重
Java 17 是继 Java 11 之后的下一个 LTS 版本。它带来了更多预览特性正式化、同时移除了一些历史遗留。
2.9.1 密封类正式发布
Java 17 正式发布了密封类(Sealed Classes),这是 Java 15/16 预览后的正式版本。现在你可以放心地在生产环境中使用密封类了。
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
| // 密封类在 Java 17 正式可用
public class SealedExprDemo {
// 密封接口:只允许下面三种实现
public sealed interface Expr permits ConstantExpr, AddExpr, MulExpr {}
// 常量表达式
public record ConstantExpr(int value) implements Expr {}
// 加法表达式
public record AddExpr(Expr left, Expr right) implements Expr {}
// 乘法表达式
public record MulExpr(Expr left, Expr right) implements Expr {}
// 编译器知道 Expr 只有三种实现,switch 可以穷尽检查
static int eval(Expr e) {
return switch (e) {
case ConstantExpr c -> c.value();
case AddExpr a -> eval(a.left()) + eval(a.right());
case MulExpr m -> eval(m.left()) * eval(m.right());
// 不需要 default!编译器确保所有情况都覆盖了
};
}
public static void main(String[] args) {
// 计算 (1 + 2) * 3 = 9
Expr expr = new MulExpr(
new AddExpr(new ConstantExpr(1), new ConstantExpr(2)),
new ConstantExpr(3));
System.out.println("结果: " + eval(expr)); // 结果: 9
}
}
|
2.9.2 废弃 Security Manager、Applet API(注意:是"废弃",不是"移除")
这里必须纠正一个流传极广的说法:Java 17 并没有"移除"Security Manager 和 Applet API,只是把它们标记为"废弃并计划移除"。
- Applet API:JDK 9(2017,JEP 289)标记废弃,JDK 17(2021,JEP 398)标记为"废弃并计划移除"。到今天(JDK 25)
java.applet 包依然存在,只是编译时会给出"已过时、待删除"的警告。 - Security Manager:JDK 17(2021,JEP 411)标记废弃并计划移除;JDK 24(2025,JEP 486)把它永久禁用——
System.setSecurityManager() 直接抛 UnsupportedOperationException,System.getSecurityManager() 永远返回 null。
所以准确的时间线是"JDK 17 废弃 → 后续版本逐步禁用/删除",写代码时千万别照着"Java 17 已经没有它们了"去理解。
2.9.3 新的 macOS 渲染引擎
Java 17 引入了新的 macOS 渲染引擎,用 Apple 的 Metal 框架取代了老的 OpenGL 渲染。这意味着 Java 应用在 macOS 上的图形性能更好。
2.9.4 随机数生成器增强
Java 17 增强了随机数生成器 API,引入了 RandomGenerator 接口和新的实现:
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
| // ⚠️ 包名要记准:随机数生成器在 java.util.random 下,不是 java.random
import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;
import java.util.random.JumpableGenerator;
public class RandomDemo {
public static void main(String[] args) {
// 获取所有可用的随机数生成器
System.out.println("可用算法:");
RandomGeneratorFactory.all()
.forEach(factory -> System.out.println(" " + factory.group() + "/" + factory.name()));
// 使用新的 Xoroshiro128PlusPlus 生成器
RandomGenerator rng = RandomGeneratorFactory.of("Xoroshiro128PlusPlus").create();
System.out.println("随机数: " + rng.nextInt(100));
// JumpableGenerator:可以"跳跃"到序列中很远的位置
// 注意:RandomGeneratorFactory.create() 返回的是 RandomGenerator,
// 想调用 jump() 必须显式转成 JumpableGenerator
JumpableGenerator jumpable =
(JumpableGenerator) RandomGeneratorFactory.of("L32X64MixMix").create();
jumpable.jump(); // 跳到下一个「时代」
System.out.println("跳跃后随机数: " + jumpable.nextInt(100));
}
}
|
2.9.5 Java 17 是 Java 11 之后的又一个 LTS 版本——企业迁移的目标版本
Java 17 是 Oracle 在 2021 年 9 月发布的 LTS 版本(继 Java 8、Java 11 之后的第三个 LTS)。以 Oracle JDK 为例,高级支持到 2026 年 9 月,延长支持到 2029 年 9 月;其他厂商的支持周期会更长一些。很多企业开始从 Java 8/11 迁移到 Java 17,享受新特性带来的好处:
- 性能提升:G1 GC 改进、ZGC 逐步成熟、JIT 编译器优化(注意:写"AOT 编译"是不对的——实验性的
jaotc AOT 编译器恰恰是在 Java 17 被移除了,见 JEP 410) - 新特性:密封类、模式匹配、文本块、record、Stream API 增强
- 安全性:移除不安全的旧 API、更强的加密算法
- 现代语法:switch 表达式、文本块、record,让代码更简洁
2.10 Java 21 LTS(2023):虚拟线程革命——21 世纪最重要的 Java 版本
「Java 21 之于 Java,就如同智能手机之于功能机。」——这是 Java 社区对 Java 21 的评价。
Java 21 是又一个 LTS 版本,也是自 Java 8 以来最重要的版本。虚拟线程(Virtual Threads)的正式发布,标志着 Java 并发编程范式的根本性转变。
2.10.1 虚拟线程(Virtual Threads)正式发布
虚拟线程是 Java 21 最大的亮点,它彻底改变了 Java 的线程模型。
背景知识:在虚拟线程出现之前,Java 的线程(称为「平台线程」)直接映射到操作系统的线程。一个线程占用 1MB 左右的栈内存,如果你需要同时处理 10000 个请求,就需要 10000 个线程——光线程栈就要消耗 10GB 内存!这导致 Java 服务器必须使用异步编程、线程池等技术来「假装」并发。
虚拟线程:也叫「轻量级线程」,由 JVM 管理,不直接绑定 OS 线程。多个虚拟线程可以共享一个 OS 线程(载体线程),大幅降低内存占用。
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
45
46
47
48
| import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
public class VirtualThreadDemo {
public static void main(String[] args) throws Exception {
// 创建虚拟线程(方式一:Thread.ofVirtual())
Thread virtualThread = Thread.ofVirtual().start(() -> {
System.out.println("我是虚拟线程!");
System.out.println("载体线程: " + Thread.currentThread());
});
// 方式二:用 ThreadFactory
ThreadFactory factory = Thread.ofVirtual().factory();
Thread t2 = factory.newThread(() -> {
System.out.println("另一个虚拟线程");
});
t2.start();
// 方式三:ExecutorService 自动管理
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 100; i++) {
final int taskId = i;
futures.add(executor.submit(() -> {
// 模拟 I/O 操作(sleep 不占 CPU,非常适合虚拟线程)
Thread.sleep(Duration.ofSeconds(1));
return "任务 " + taskId + " 完成";
}));
}
for (Future<?> f : futures) {
System.out.println(f.get());
}
}
// 虚拟线程 vs 平台线程的内存对比
// 平台线程:每个约 1MB 栈
// 虚拟线程:每个约 200B~1KB 栈(按需扩展)
// 10 万并发:平台线程需要 ~100GB 内存,虚拟线程只需要 ~1GB
System.out.println("主线程结束");
}
}
|
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
| // 一个小而完整的 HTTP 服务:每个请求交给一个虚拟线程处理
import com.sun.net.httpserver.HttpServer;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executors;
public class VirtualThreadHttpServer {
public static void main(String[] args) throws Exception {
// 端口 8080,第二个参数 0 表示使用系统默认的 backlog
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
// 关键的一行:把执行器换成"每个任务一个虚拟线程"
server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
server.createContext("/", exchange -> {
byte[] body = "Hello from a virtual thread!\n".getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200, body.length);
try (var out = exchange.getResponseBody()) {
out.write(body);
}
});
server.start();
System.out.println("服务器已启动:http://localhost:8080/");
System.out.println("每个请求都会由一个独立的虚拟线程处理");
}
}
|
重要提示:虚拟线程适合 I/O 密集型任务(网络请求、文件操作、数据库查询),不适合 CPU 密集型任务(计算密集)。对于 CPU 密集型,还是需要平台线程和并行流。
2.10.2 Pattern Matching for switch 正式版
Java 21 正式发布了 Pattern Matching for switch,让 switch 语句可以匹配模式:
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
| public class PatternSwitchDemo {
// 1. 用模式替代"先 instanceof 再强转"
static String describe(Object obj) {
return switch (obj) {
// when 是"守卫条件",让同一个类型可以走不同分支
case String s when s.length() > 5 -> "长字符串: " + s;
case String s -> "短字符串: " + s;
case Integer i -> "数字: " + i;
// 同时匹配 null 和"其他所有情况"
case null, default -> "其他";
};
}
// 2. record + switch 模式匹配:密封接口保证 switch 可以穷尽
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double a, double b, double c) implements Shape {}
static double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Triangle t -> {
// 海伦公式
double s2 = (t.a() + t.b() + t.c()) / 2;
yield Math.sqrt(s2 * (s2 - t.a()) * (s2 - t.b()) * (s2 - t.c()));
}
};
}
public static void main(String[] args) {
System.out.println(describe("hello"));
System.out.println(describe(42));
System.out.println(area(new Rectangle(3, 4))); // 12.0
System.out.println(area(new Circle(1))); // 3.141592653589793
}
}
|
2.10.3 Record Patterns:record 可以用在 pattern matching 里
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
| public class RecordPatternDemo {
record Point(int x, int y) {}
record Line(Point start, Point end) {}
// 1. instanceof + 记录模式:一步完成类型检查和字段解构
static void printSum(Object obj) {
if (obj instanceof Point(int x, int y)) {
// 直接拿到 x 和 y,不用再强转和调 getter
System.out.println("Sum = " + (x + y));
}
}
// 2. 结合 switch 使用,还能嵌套解构
static String describe(Object obj) {
return switch (obj) {
case Point(int x, int y) when x == y -> "对角线上的点";
case Point(int x, int y) -> "普通点 (" + x + ", " + y + ")";
// 嵌套记录模式:直接解构出线段的两个端点坐标
case Line(Point(int x1, int y1), Point(int x2, int y2))
-> "线段 (" + x1 + "," + y1 + ") → (" + x2 + "," + y2 + ")";
case null -> "空";
default -> "其他形状";
};
}
public static void main(String[] args) {
printSum(new Point(3, 4)); // Sum = 7
System.out.println(describe(new Point(5, 5))); // 对角线上的点
System.out.println(describe(new Line(new Point(0, 0), new Point(1, 2))));
}
}
|
2.10.4 Scoped Values 与结构化并发:比 ThreadLocal 更安全的数据传递(预览特性)
⚠️ 先把版本说清楚:ScopedValue 与结构化并发(StructuredTaskScope)都是预览特性,至今没有转正。它们在 Java 21 首次预览,之后每个版本都在修订。使用它们必须加 --enable-preview 参数,而且编译和运行必须用同一个 JDK 版本(预览特性是按版本绑定的)。写业务代码时请谨慎采用。
ThreadLocal 的问题:线程池环境下,ThreadLocal 的值可能被错误复用;值传递也不够安全。
ScopedValue 的优势:值与代码块的作用域绑定,出了作用域就自动失效,不需要手动 remove();配合结构化并发还能安全地传给子任务。
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
45
46
47
| // ⚠️ 下面这段代码需要 --enable-preview 才能编译运行
// 注意:ScopedValue 位于 java.lang 包,不需要 import!
import java.util.concurrent.StructuredTaskScope;
// ScopedValue:线程安全的数据容器
public class ScopedValueDemo {
// 定义一个 ScopedValue:注意创建方式是 newInstance(),不是 empty()
static final ScopedValue<String> CURRENT_USER = ScopedValue.newInstance();
public static void main(String[] args) {
// 作用域外取值:用 orElse 提供兜底值
String result = CURRENT_USER.orElse("Anonymous");
System.out.println("作用域外的用户: " + result);
// 绑定值并运行:API 是 ScopedValue.where(...).run(...)
ScopedValue.where(CURRENT_USER, "Alice").run(() -> {
System.out.println("作用域内的用户: " + CURRENT_USER.get());
callService(); // 被调用的子方法也能读到这个值
});
// ScopedValue 在结构化并发中特别有用。
// ⚠️ 结构化并发仍是预览 API,各版本写法不同:
// Java 21~24:new StructuredTaskScope.ShutdownOnFailure()
// Java 25+ :StructuredTaskScope.open(...)(API 被重新设计过)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<Integer> f1 = scope.fork(() -> {
return doTask1(CURRENT_USER.get()); // 自动继承 ScopedValue
});
Future<Integer> f2 = scope.fork(() -> {
return doTask2(CURRENT_USER.get());
});
scope.join();
System.out.println("结果: " + (f1.resultNow() + f2.resultNow()));
} catch (Exception e) {
e.printStackTrace();
}
}
static void callService() {
// 子方法可以直接访问 ScopedValue
System.out.println("服务调用者: " + CURRENT_USER.get());
}
static Integer doTask1(String user) { return 1; }
static Integer doTask2(String user) { return 2; }
}
|
2.10.5 String Templates(字符串模板):预览后被撤回,至今没有正式版
⚠️ 这一条必须纠正:网络上大量文章说"String Templates 会在 Java 26 转正",这是不准确的。真实情况是:
String Templates 在 Java 21 首次预览(JEP 430)、Java 22 第二次预览(JEP 459),随后被撤回——从 Java 23 起,STR."..." 这套语法已经不在 JDK 里了,也没有进入任何后续正式版本。JDK 25 中同样没有它。
也就是说,下面这段代码在今天的任何 JDK(包括最新的正式版)里都编译不过。保留它只是为了说明"曾经有过这样一个设计",写代码时请改用 String.format()、StringBuilder 或文本块。
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
| // ⚠️ 已被撤回的预览语法(仅 Java 21/22 的预览版能用,且需要 --enable-preview)
// 在 Java 23 及以后的版本中,这段代码无法编译
// 简单的模板
String name = "Alice";
String greeting = STR."Hello, \{name}!"; // "Hello, Alice!"
// 表达式
int x = 10, y = 20;
String math = STR."\{x} + \{y} = \{x + y}"; // "10 + 20 = 30"
// 复杂表达式
String multi = STR."""
第一行: \{x}
第二行: \{y}
计算结果: \{x * y}
""";
// TemplateProcessor 自定义模板处理器
// FMT 处理器:格式化数字
String formatted = FMT."""
整数: %d\{x}
浮点: %.2f\{(double)x / y}
十六进制: %x\{x}
""";
|
2.10.6 Foreign Function & Memory API(Java 22 正式版)
⚠️ 版本纠正:FFM API 在 Java 21 是预览,在 Java 22 就正式转正了(JEP 454),并不是"Java 26 正式发布"。它让 Java 可以直接调用 native 代码、安全地操作堆外内存,目标是逐步取代 JNI。
1
2
3
4
5
6
7
8
9
10
11
12
13
| // FFM API(Java 22 起为正式特性)
// 使用 MemorySegment 和 Arena
// 分配堆外内存
// 注意要用 Arena.ofConfined() 这类"可关闭"的 Arena;
// Arena.global() 是全局共享的,关闭它会抛出异常
try (Arena arena = Arena.ofConfined()) {
MemorySegment segment = arena.allocate(1024);
// 操作内存
segment.set(ValueLayout.JAVA_INT, 0, 42);
int value = segment.get(ValueLayout.JAVA_INT, 0);
System.out.println("读取值: " + value);
}
|
这个 API 的目标是取代 JNI(Java Native Interface),让 Java 与 C/C++ 库交互更安全、更高效。
2.10.7 Sequenced Collection:集合有了统一的顺序访问接口
Java 21 引入了「序列集合」概念,统一了 List、Deque、Set 的顺序访问接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| import java.util.ArrayList;
import java.util.List;
public class SequencedCollectionDemo {
public static void main(String[] args) {
// List 从 Java 21 起实现了 SequencedCollection,
// 于是有了统一的首尾访问方法(Deque、LinkedHashSet、SortedSet 同理)
List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
list.addFirst(0); // [0, 1, 2, 3]
list.addLast(4); // [0, 1, 2, 3, 4]
int first = list.getFirst(); // 0
int last = list.getLast(); // 4
System.out.println("first=" + first + ", last=" + last);
// reversed() 返回的是"反向视图",不是副本:
// 改原列表会同步反映到 reversed 上
List<Integer> reversed = list.reversed();
System.out.println(reversed); // [4, 3, 2, 1, 0]
list.addFirst(-1);
System.out.println(reversed); // [-1, 4, 3, 2, 1, 0] ← 视图跟着变了
}
}
|
📌 补充:SequencedCollection 是 JDK 自带的接口(java.util.SequencedCollection),它定义的方法包括 reversed()、addFirst/addLast、getFirst/getLast、removeFirst/removeLast。Map 对应的接口是 SequencedMap,提供 putFirst/putLast、sequencedKeySet()、reversed() 等——注意 Map 本身没有 getFirst/getLast。
2.10.8 ZGC 和 Shenandoah GC(注意:它们不是 Java 21 才转正的)
⚠️ 版本纠正:ZGC 和 Shenandoah 都是在 Java 15 就转正了(JEP 377、JEP 379),并不是 Java 21 才"正式发布"。它们真正与 Java 21 相关的变化是:ZGC 在 Java 21 引入了分代模式(JEP 439,-XX:+UseZGC -XX:+ZGenerational,后来成为默认行为)。
- ZGC:低延迟 GC,适合大内存(64GB+)应用
- Shenandoah:低延迟 GC,适合中等内存应用,Red Hat 主导
1
2
3
4
5
| # 启用 ZGC
java -XX:+UseZGC -Xmx64g MyApp
# 启用 Shenandoah
java -XX:+UseShenandoahGC -Xmx16g MyApp
|
2.11 Java 2226(20242026):持续进化
Java 的发布节奏已经稳定在每六个月一个大版本。下面这一节按版本梳理近年变化,涉及版本状态的判断都以可实测的 JDK 为准(本地 JDK 25 实测:ScopedValue 可用、StructuredTaskScope 仍是预览 API、JDK 中没有 java.lang.StringTemplate)。
2.11.1 Java 22(2024.3):FFM API 与 Unnamed Variables 转正
Java 22 转正的特性:
- Foreign Function & Memory API(JEP 454):正式成为标准 API,取代了 JNI 的绝大部分日常用途,让 Java 调用本地库、访问堆外内存有了安全且高效的正规途径。
- Unnamed Variables & Patterns(JEP 456):用
_ 表示「这个变量我不需要」。
同时继续处于预览或孵化状态的还有:String Templates(第二次预览,JEP 459)、Statements before super()(JEP 447)、Implicitly Declared Classes(JEP 463)、Class-File API、Stream Gatherers、Structured Concurrency、Scoped Values。
⚠️ 常见误传:网上流传的 “Statement Expressions”(可以在任意表达式位置写 { ... } 代码块,例如 System.out.println({ int m = ...; m; }))不是 Java 22 的特性,也不存在于任何 JDK 中,这段代码无法通过编译。
FFM API 示例(Java 22 起标准 API,无需预览参数):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
public class FfmDemo {
public static void main(String[] args) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment segment = arena.allocate(8);
segment.set(ValueLayout.JAVA_INT, 0, 42);
int value = segment.get(ValueLayout.JAVA_INT, 0);
System.out.println("读取值: " + value); // 读取值: 42
}
}
}
|
Unnamed Variables 示例(JDK 25 实测通过):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| public class UnnamedVariablesDemo {
record Point(int x, int y) {}
public static void main(String[] args) {
String[] parts = "a-b-c".split("-");
String _ = parts[1]; // 明确表示「这个值拿到但不用」
System.out.println(parts[0] + parts[2]); // ac
Object o = new Point(3, 4);
if (o instanceof Point(int x, int _)) { // 模式里也能用 _ 忽略某个分量
System.out.println("x = " + x); // x = 3
}
// 另一个典型场景:catch 里不关心异常对象
try {
Integer.parseInt("abc");
} catch (NumberFormatException _) {
System.out.println("转换失败,忽略具体异常信息");
}
}
}
|
2.11.2 Java 23(2024.9):String Templates 被撤回
String Templates(字符串模板)在 Java 23 被撤下。它在 Java 21、22 各预览过一次(JEP 430、JEP 459),原计划继续预览的 JEP 465 最终被撤销,此后各版本 JDK 都不再包含这个特性——在 JDK 25 里,java.lang.StringTemplate 这个类根本不存在。所以「Java 26 会正式发布 String Templates」这样的说法是不可信的。
Java 23 的其他变化:
- 文档注释支持 Markdown(预览,JEP 467)
- ZGC 默认使用分代模式(JEP 474)
- 弃用
sun.misc.Unsafe 的内存访问方法(JEP 471),为彻底移除做准备 - Module Import Declarations 首次预览(JEP 476,Java 25 转正)
2.11.3 Java 24(2025.3):Class-File API、Stream Gatherers 转正
- Class-File API(JEP 484):正式发布,标准库可以直接读写、生成 class 文件,不再必须依赖 ASM。
- Stream Gatherers(JEP 485):正式发布,为 Stream 添加自定义中间操作的能力。
- 永久禁用 SecurityManager(JEP 486):该项在 Java 17 就已弃用(JEP 411),Java 24 起无法再通过
-Djava.security.manager 启用。 - 虚拟线程不再因
synchronized 而钉住载体线程(JEP 491):虚拟线程在同步块中阻塞时不再占用平台线程。 - ZGC 移除非分代模式(JEP 490),AOT 类加载与链接支持(JEP 483)。
⚠️ 版本纠正:Scoped Values 在 Java 24 仍然是预览特性,它到 Java 25 才转正;Structured Concurrency 到 Java 25 也还是预览特性(StructuredTaskScope 在 JDK 25 上不加 --enable-preview 会直接报错)。另外,「Smart Enum Values」并不是一个 Java 特性——下面那种写法只是普通的穷尽 switch 表达式(Java 14 起支持,Java 21 起用于模式匹配)。
枚举上的穷尽 switch(不是新特性,但很好用):
1
2
3
4
5
6
7
8
9
10
11
12
| enum Color {
RED, GREEN, BLUE;
Color opposite() {
// 覆盖全部常量后不必写 default,编译器会检查穷尽性
return switch (this) {
case RED -> GREEN;
case GREEN -> RED;
case BLUE -> RED;
};
}
}
|
2.11.4 Java 25(2025.9,LTS):Scoped Values、简洁源文件转正
Java 25 是新的 LTS 版本,转正的特性包括:
- Scoped Values(JEP 506):正式发布,用来在虚拟线程之间安全地共享不可变数据,API 形如
ScopedValue.where(KEY, value).run(...),比 ThreadLocal 更可控。 - Module Import Declarations(JEP 511):正式发布,写一行
import module java.base; 就能把整个模块的包引入进来。 - Compact Source Files and Instance Main Methods(JEP 512):正式发布,可以直接写
void main() { ... } 而不必声明类,非常适合教学和脚本。 - Flexible Constructor Bodies(JEP 513):正式发布,构造函数里可以把校验语句写在
super(...) 之前。 - Compact Object Headers(JEP 519):正式发布,降低对象头开销。
- 移除 32 位 x86 支持(JEP 503)。
以下代码在 JDK 25 上无需任何预览参数即可运行:
1
2
3
4
5
6
7
8
| import module java.base;
import java.lang.ScopedValue;
void main() {
ScopedValue<String> USER = ScopedValue.newInstance();
ScopedValue.where(USER, "alice").run(() -> System.out.println("当前用户: " + USER.get()));
System.out.println("简洁源文件 + 模块导入 + ScopedValue 都可用");
}
|
仍在预览中的有:Structured Concurrency(第五次预览)、Primitive Types in Patterns(第三次预览)、Key Derivation API 等。
2.11.5 关于 Java 26 的说法:请以官方 JEP 列表为准
早期资料里关于「Java 26」的内容有不少错误或推测,至少下面几条可以肯定是不对的:
| 常见说法 | 实际情况 |
|---|
| FFM API 在 Java 26 才正式发布 | ❌ FFM API 在 Java 22 就已转正(JEP 454) |
catch (Exception e) when (...) 异常过滤 | ❌ Java 根本没有这种语法,when 不能出现在 catch 之后(javac 会直接报 '{' expected) |
| String Templates 在 Java 26 正式发布 | ❌ 该特性在 Java 23 已被撤回,JDK 25 中不存在 java.lang.StringTemplate |
| Structured Concurrency 已是最终版 | ❌ 截至 JDK 25 它仍是预览特性 |
想了解某个 JDK 版本到底包含哪些特性,最可靠的做法是直接查该版本的 JEP 列表与官方发行说明,或者在本机用 java -version 对应的 JDK 亲手编译一次——很多「新特性」只要写几行代码就能验证真假。
未来展望:
- Project Leyden:AOT 相关的启动与内存占用优化,相关能力正逐步进入 JDK
- Project Loom:虚拟线程已落地,后续继续优化其调度与调试体验
- Project Amber:语法增强,包括模式匹配扩展等
- Project Panama / Babylon:前者产出了 FFM API,后者在探索更自然的外部代码与 GPU 加速支持
本章小结
回顾 Java 从 1996 到 2026 这三十年的发展历程,我们可以清晰地看到几条主线:
语法现代化:从 Java 5 的泛型、注解、枚举,到 Java 8 的 Lambda、Stream,再到 Java 14+ 的 record、密封类、模式匹配——Java 的语法越来越简洁、表达力越来越强。
并发模型革新:从原始的 Thread 到 ExecutorService,从 J.U.C 到 CompletableFuture,再到 Java 21 的虚拟线程——Java 的并发编程经历了三次革命。
平台轻量化:从 Applet 到 Java Web Start,从 J2EE 到 Spring,从模块化到 jlink 裁剪运行时——Java 一直在追求更轻、更快、更灵活。
发布节奏稳定:Oracle 采用的每六个月一个版本、LTS 每两年的策略,让 Java 既能快速迭代,又能保持稳定。
向后兼容:Java 30 年,始终保持惊人的向后兼容性。1996 年的 JAR 文件,今天的 JVM 依然能跑。这种稳定性,是 Java 成功的基石。
各版本的「江湖地位」:
- Java 5:语法革命奠基者
- Java 8:现代化转折点(最重要版本)
- Java 9:模块化开山之作
- Java 11:LTS 迁移目标
- Java 17:成熟稳重 LTS
- Java 21:虚拟线程革命(21 世纪最重要版本)
- Java 26:FFM API 正式版、功能完善
Java 不是一门靠「追新」取胜的语言。它的成功在于:稳扎稳打、持续演进、向后兼容。三十年过去了,Java 依然是全球最流行的编程语言之一,依然在企业级应用、后端开发、Android 开发等领域占据统治地位。
正如 James Gosling(Java 之父)所说:「Java 不是最好的语言,但它是一门将正确的事情做对的语言。」
下一章,我们将深入 Java 的核心机制——JVM 虚拟机。理解 JVM,才能真正理解 Java 的强大之处。