Skip to content
<

Java 9 于 2017年9月发布,引入了模块系统这一重大⁢特性,同时也带来了许多实用的改进。虽然模⁡块系统在企业中应用不多,但集合工厂方法等⁡特性却非常实用

正式特性

【了解】模块系统

在模块系统出现之前,传统 Java 应用只能依赖 classpath 来管理依⁢赖,所有的类都在同一个类路径下,任何类都可以访问任何⁡其他类,这种 "全局可见性" 在大型项目中会导致代码⁡耦合严重、依赖关系混乱、运行时才发现 ClassNotFoundException 等问题。

模块系统允许我们将代码组织成模⁢块,每个模块都有明确⁡的依赖关系和导出接口⁡,让大型应用的架构变得更加清晰和可维护。

模块系统通过 module-info.java 文件来定义模块的边界,明确声明哪些包对外开放,哪些依赖是必需的,这样就形成了强封装的架构。

比如一个用户管理模块只暴露用户服务接⁢口,而内部的数据访问层对⁡其他模块完全不可见,这种⁡设计让系统的层次结构更加清晰,也避免了意外的跨层调用。

java
module user.management {  
    // 只导出 service 包,dao 包对外不可见  
    exports com.company.user.service;  
      
    // 依赖其他模块  
    requires java.base;  
    requires database.connection;  
}

此外,模块系统还带来了更好的性⁢能优化,JVM 可以⁡在启动时只加载必需的⁡模块,减少内存占用和启动时间(适合云原生应用)。

但是,模块系统在企业中用的比较少,目前大多数企业还是使用传统的 Maven/Gradle + JAR 包的方式管理依赖,改造项目的成本 > 模块系统带来的实际收益,所以仅作了解就好。

【必备】集合工厂方法

Java 9 为集合类添加了⁢便捷的工厂方法,能⁡够轻松创建不可⁡变集合。

在这之前,创建不可变集合还是⁢比较麻烦的,很多开⁡发者会选择依赖第三⁡方库(比如 Google Guava)。

传统的不可变集合创建方式:

java
// Java 9 之前创建不可变集合的方式  
List<String> oldList = new ArrayList<>();  
oldList.add("苹果");  
oldList.add("香蕉");  
oldList.add("鱼皮");  
List<String> immutableList = Collections.unmodifiableList(oldList);  
  
// 或者使用 Google Guava 等第三方库  
List<String> guavaList = ImmutableList.of("苹果", "香蕉", "鱼皮");

有了 Java 9 的工厂方⁢法,创建不可变集合⁡简直不要太简单!

java
// Java 9 的简洁写法  
List<String> fruits = List.of("苹果", "香蕉", "鱼皮");  
Set<Integer> numbers = Set.of(1, 2, 3, 4, 5);  
Map<String, Integer> scores = Map.of(  
    "张三", 85,   
    "鱼皮", 92,   
    "狗剩", 78  
);

这些集合是真正不可变的,任何修改操作都会抛出 UnsupportedOperationException 异常。

如果想创建包含大量元素的不可⁢变 Map,可以使⁡用 ofEntri⁡es 方法:

java
Map<String, String> largeMap = Map.ofEntries(  
    Map.entry("key1", "value1"),  
    Map.entry("key2", "value2"),  
    Map.entry("key3", "value3")  
    // ... 可以有任意多个  
);

【了解】JShell 交互工具

JShell 是 Java 9 引入的一个交互⁢式工具,在这个工具出现之前,我⁡们要测试一小段 Java 代码⁡,必须创建完整的类和 main 方法,编译后才能运行。

有了 JShell,我们可以⁢像使用 Pytho⁡n 解释器一样使用⁡ Java,对于学习调试有点儿用(但不多)。

直接在命令行输入 jshell 就能使用了:

【了解】HTML5 Javadoc

Java 9 更新了 Java⁢doc 工具,生成的⁡文档符合 HTML5⁡ 标准,提供了更好的可访问性和现代化的外观:

java
/**  
 * 计算圆的面积  
 *   
 * @param radius 圆的半径,必须大于 0  
 * @return 圆的面积  
 * @throws IllegalArgumentException 如果半径小于等于 0  
 * @since 9  
 */  
public double calculateArea(double radius) {  
    if (radius <= 0) {  
        throw new IllegalArgumentException("半径必须大于 0");  
    }  
    return Math.PI * radius * radius;  
}

生成的 HTML5 文档支持更好的搜索功能和响应式设计。

【了解】Javadoc 搜索

Java 9 的 Javad⁢oc 增加了搜索功⁡能,可以快速查找类⁡、方法、字段等:

  • 支持模糊搜索

  • 支持按类型过滤(类、方法、字段等)

  • 提供搜索建议和自动补全

【了解】紧凑字符串

Java 9 优化了 String 类的内部实现。之前 String 内部使用 char[] 数组存储字符,每个字符占用 2 字节。

Java 9 引入了紧凑字符串特性,String 内部改用 byte[] 数组 + 编码标志:

java
public final class String {  
    private final byte[] value;  // 字符数据  
    private final byte coder;    // 编码标志:0=LATIN1, 1=UTF16  
      
    // ... 其他代码  
}
  • 如果字符串只包含 Latin1 字符(ASCII),每个字符只占用 1 字节

  • 如果包含其他 Unicode 字符,才使用 UTF-16 编码,每个字符占用 2 字节

这个优化对开发者完全透明,可⁢以节省大约 10-⁡15% 的堆内⁡存使用。

【了解】Stack-Walking API

Java 9 提供了新的 S⁢tack-Walk⁡ing API,可⁡以更高效地遍历调用栈:

java
import java.lang.StackWalker;  
import java.util.List;  
import java.util.stream.Collectors;  
  
public class StackWalkingExample {  
    public static void main(String[] args) {  
        methodA();  
    }  
      
    static void methodA() {  
        methodB();  
    }  
      
    static void methodB() {  
        // 获取调用栈信息  
        StackWalker walker = StackWalker.getInstance();  
          
        // 打印调用栈  
        walker.forEach(frame ->   
            System.out.println(frame.getClassName() + "." + frame.getMethodName())  
        );  
          
        // 获取调用者信息  
        StackWalker.StackFrame caller = walker.getCallerFrame();  
        System.out.println("调用者:" + caller.getMethodName());  
          
        // 获取指定深度的栈帧  
        List<String> methods = walker.walk(frames ->   
            frames.limit(3)  
                  .map(StackWalker.StackFrame::getMethodName)  
                  .collect(Collectors.toList())  
        );  
        System.out.println("方法调用链:" + methods);  
    }  
}

相比传统的 Thread.currentThread().getStackTrace(),新的 API 性能更好,功能更强大。

【了解】更多并发更新

Java 9 对并发工具类进行了增强:

CompletableFuture 增强

java
// 支持延迟执行  
CompletableFuture<String> future = CompletableFuture  
    .delayedExecutor(1, TimeUnit.SECONDS)  
    .execute(() -> System.out.println("延迟执行"));  
  
// 支持超时处理  
CompletableFuture<String> timeoutFuture = CompletableFuture  
    .supplyAsync(() -> {  
        // 模拟长时间运行的任务  
        try { Thread.sleep(2000); } catch (InterruptedException e) {}  
        return "完成";  
    })  
    .orTimeout(1, TimeUnit.SECONDS)  // 1秒超时  
    .exceptionally(throwable -> "超时了");

Flow API

Java 9 引入了响应式流⁢(Reactive⁡ Streams)⁡的标准实现:

java
import java.util.concurrent.Flow;  
import java.util.concurrent.SubmissionPublisher;  
  
public class FlowExample {  
    public static void main(String[] args) throws InterruptedException {  
        // 创建发布者  
        SubmissionPublisher<String> publisher = new SubmissionPublisher<>();  
          
        // 创建订阅者  
        Flow.Subscriber<String> subscriber = new Flow.Subscriber<String>() {  
            private Flow.Subscription subscription;  
              
            @Override  
            public void onSubscribe(Flow.Subscription subscription) {  
                this.subscription = subscription;  
                subscription.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("Hello");  
        publisher.submit("World");  
        publisher.close();  
          
        Thread.sleep(1000);  
    }  
}

接口私有方法

【了解】接口私有方法

思考一个问题,如果某个接口中⁢的默认方法需要复用⁡代码,你会怎么做呢⁡?

比如让你来优化下面这段代码:

java
public interface Calculator {  
      
    default double calculateRectangleArea(double width, double height) {  
        // 重复的验证逻辑  
        if (width <= 0 || height <= 0) {  
            throw new IllegalArgumentException("宽度和高度必须为正数");  
        }  
        return width * height;  
    }  
      
    default double calculateTriangleArea(double base, double height) {  
        // 重复的验证逻辑  
        if (base <= 0 || height <= 0) {  
            throw new IllegalArgumentException("底边和高度必须为正数");  
        }  
        return base * height / 2;  
    }  
}

你会把重复的验证逻辑写在哪里呢?

答案很简单,写在一个外部工具⁢类里,或者在接口内⁡再写一个通用的验证⁡方法:

java
public interface Calculator {  
    // 通用的验证方法  
    default void validate(double x, double y) {  
        if (x <= 0 || y <= 0) {  
            throw new IllegalArgumentException("必须为正数");  
        }  
    }  
}

但这种方式存在一个问题,validate 作为 default ⁢方法,它会成为接口的公共 API,所有实现⁡类都能访问到!其实这个方法只需要在接口内可⁡以使用就够了。

Java 9 解决了这个问题,允许在接口⁢中定义私有方法(以及私有静态方⁡法)。

java
public interface Calculator {  
    // 私有方法  
    private void validate(double x, double y) {  
        if (x <= 0 || y <= 0) {  
            throw new IllegalArgumentException("必须为正数");  
        }  
    }  
  
    // 私有静态方法  
    private static void validatePositive(double x, double y) {  
        if (x <= 0 || y <= 0) {  
            throw new IllegalArgumentException("必须为正数");  
        }  
    }  
}

这样一来,接口内部可以优雅地⁢复用代码,同时保持⁡接口对外的简洁性。

💡 这里也能看出 Java 的演进很谨慎,先⁢允许 default 方法(J⁡ava 8),再允许 priv⁡ate 方法(Java 9),每一步都有明确的设计考量。

【了解】改进的 try-with-resources

Java 9 改进了 try-with-reso⁢urces 语句,在这之前,我们⁡不能在 try 子句中使用外部定⁡义的变量,必须在 try 括号内重新声明,会让代码变得冗余。

java
// Java 9 之前  
public void readFile(String filename) throws IOException {  
    BufferedReader reader = Files.newBufferedReader(Paths.get(filename));  
    try (BufferedReader br = reader) {  // 需要重新赋值  
        br.lines().forEach(System.out::println);  
    }  
}

Java 9 的改进让代码更加简洁:

java
// Java 9  
public void readFile(String filename) throws IOException {  
    BufferedReader reader = Files.newBufferedReader(Paths.get(filename));  
    try (reader) {  // 直接使用 effectively final 变量  
        reader.lines().forEach(System.out::println);  
    }  
}

而且还可以同时使用多个变量:

java
public void processFiles(String file1, String file2) throws IOException {  
    var reader1 = Files.newBufferedReader(Paths.get(file1));  
    var reader2 = Files.newBufferedReader(Paths.get(file2));  
    try (reader1; reader2) {  // 可以使用多个变量  
        String line1 = reader1.readLine();  
        String line2 = reader2.readLine();  
        while (line1 != null && line2 != null) {  
            System.out.println(line1 + " | " + line2);  
            line1 = reader1.readLine();  
            line2 = reader2.readLine();  
        }  
    }  
}

其他改进

【了解】Stream API 增强

Java 9 为 Strea⁢m API 添加了⁡几个实用方法:

java
// takeWhile:获取满足条件的元素直到遇到不满足的  
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8);  
List<Integer> taken = numbers.stream()  
    .takeWhile(n -> n < 5)  // 取小于5的元素  
    .collect(Collectors.toList());  
System.out.println(taken); // [1, 2, 3, 4]  
  
// dropWhile:跳过满足条件的元素直到遇到不满足的  
List<Integer> dropped = numbers.stream()  
    .dropWhile(n -> n < 5)  // 跳过小于5的元素  
    .collect(Collectors.toList());  
System.out.println(dropped); // [5, 6, 7, 8]  
  
// ofNullable:安全地创建可能为null的流  
String nullString = null;  
long count = Stream.ofNullable(nullString).count();  
System.out.println(count); // 0  
  
// iterate:带条件的无限流  
List<Integer> evenNumbers = Stream.iterate(0, n -> n < 10, n -> n + 2)  
    .collect(Collectors.toList());  
System.out.println(evenNumbers); // [0, 2, 4, 6, 8]

【了解】Optional 增强

Optional 也得到了一些有用的增强:

java
Optional<String> optional1 = Optional.of("Hello");  
Optional<String> optional2 = Optional.empty();  
  
// ifPresentOrElse:有值时执行一个动作,没值时执行另一个动作  
optional1.ifPresentOrElse(  
    value -> System.out.println("有值:" + value),  
    () -> System.out.println("没有值")  
);  
  
// or:提供备选的Optional  
Optional<String> result = optional2.or(() -> Optional.of("默认值"));  
  
// stream:将Optional转换为Stream  
List<String> values = List.of(optional1, optional2).stream()  
    .flatMap(Optional::stream)  
    .collect(Collectors.toList());  
System.out.println(values); // [Hello]

总结

Java 9 虽然引入了模块系统这一重大特性,但在实际企业开发中应用并不广⁢泛。不过,集合工厂方法、JShell、接口私有⁡方法等特性都很实用,特别是集合工厂方法大大简化了⁡不可变集合的创建。Stream 和 Optional 的增强也让函数式编程更加完善。

Java 9 标志着 Jav⁢a 开始采用 6 ⁡个月一次的发布周期⁡,后续版本的迭代速度明显加快。