小语言增强(JDK 9)#
版本信息
- 最低 JDK:9(JEP 213「Milling Project Coin」,11 LTS 承接)
- JEP:JEP 213: Milling Project Coin
为什么需要#
JEP 213 是 JDK 7「Project Coin」的一批小幅打磨(故称「Milling」磨一磨),其中两个最常用:
- try-with-resources 只能用「新声明」:JDK 8 要求资源必须在
try (...)里当场声明(try (BufferedReader br = ...))。若资源是外部传进来或先创建好的已有变量,只能再包一层或重新声明,别扭。 - 接口没有私有方法:多个
default方法想共用一段逻辑,只能把它写成另一个default(于是成了公开 API 的一部分,泄露实现)或拷贝。JDK 9 起接口可声明private方法,干净地复用而不暴露。
语法#
examples/small-lang/ 里真实可运行的源码(snippets 嵌入,与示例零漂移):
package com.javamodern.smalllang;
import java.io.BufferedReader;
import java.io.StringReader;
// JDK 9 小语言增强(JEP 213):
// 1) try-with-resources 可用「事实上 final」的已有变量(不再要求必须是新声明)
// 2) 接口可定义 private 方法,供多个 default 方法共享逻辑
public class SmallLangDemo {
// 2) 接口 private 方法(JDK 9):default 方法共用一段逻辑,又不想暴露成 public API
public interface Greeter {
default String hi(String name) {
return line("Hi", name) + "\n" + line("Bye", name);
}
private String line(String prefix, String name) { // JDK 9 才允许接口 private 方法
return prefix + ", " + name;
}
}
// 1) try-with-resources 用「事实上 final」变量(JDK 9):br 在 try 外声明、之后未再赋值
public static int countLines(String text) throws Exception {
BufferedReader br = new BufferedReader(new StringReader(text)); // 已声明
try (br) { // (1) JDK 9:br 事实 final,自动关闭
int n = 0;
while (br.readLine() != null) {
n++;
}
return n;
}
}
public static String greet(String name) {
return new Greeter() {}.hi(name); // 匿名实现,default 方法内部调用 private 辅助
}
public static void main(String[] args) throws Exception {
System.out.println("lines = " + countLines("a\nb\nc"));
System.out.println(greet("Java").replace("\n", " / "));
}
}
- :material-lightbulb:
try (br)里的br是之前声明、之后未再赋值的「事实上 final」变量——JDK 9 允许它作为 try-with-resources 资源;Greeter接口的line(...)是private方法,供default方法内部共用。
运行 SmallLangDemo.main 的实测输出(JDK 11):
lines = 3
Hi, Java / Bye, Java
与 JDK 8 旧写法对比#
BufferedReader br = Files.newBufferedReader(path);
try (BufferedReader br2 = br) { // 只能重新声明一个变量接住它,别扭
...
}
interface Greeter {
default String hi(String n) { return shared(n); }
default String bye(String n) { return shared(n); }
// 想复用「shared」只能写成 default——但它就成了对外 API
default String shared(String n) { ... }
}
BufferedReader br = Files.newBufferedReader(path);
try (br) { // br 事实上 final,直接用
...
}
interface Greeter {
default String hi(String n) { return line("Hi", n); }
private String line(String p, String n) { return p + ", " + n; } // 不进对外 API
}
底层原理#
- try-with-resources 事实 final:编译器对「资源表达式」放宽——只要该变量是
final或「事实上 final」(声明后未被重新赋值),即可作资源;编译器照样在末尾生成close()调用与异常处理,与当场声明等价。 - 接口 private 方法:接口的
private方法是有方法体、非抽象的,仅供本接口的default/static方法调用;字节码层它们是普通的 private 方法(也可private static供静态方法复用),不进入接口的公开契约,外部无法实现/调用。
graph LR
T["try (br):事实 final 变量<br/>编译器照常生成 close"] --> A["JDK 9 放宽资源表达式"]
I["interface private 方法<br/>有方法体、不进对外 API"] --> D["供 default/static 复用逻辑"]
style A fill:#bbdefb
style I fill:#c8e6c9
常见坑 / 最佳实践#
- 必须「事实 final」:
try (br)里的变量一旦在之后被重新赋值,就不再是 effectively final,编译报错。 - private 不能 abstract:接口的 private 方法有方法体(非抽象);要复用静态逻辑用
private static。 - 不在 public 契约里:private 方法只是实现细节,外部实现该接口时看不到也调不到——别指望子类覆写它。
- try-with-resources 仍会关闭:用事实 final 变量与当场声明一样,离开 try 块自动
close(),资源被复用时尤其要留意(关掉后外部再用会报已关闭)。
小结#
JEP 213 的这两处打磨虽小,却消掉了 JDK 8 里两处常见别扭:try-with-resources 不再死板要求当场声明,接口能干净地复用逻辑。属于「用了就回不去」的小改进。