跳转至

小语言增强(JDK 9)#

版本信息

为什么需要#

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", " / "));
    }
}
  1. :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 不再死板要求当场声明,接口能干净地复用逻辑。属于「用了就回不去」的小改进。

参考#