Java10 新特性详解与实践

262 阅读5分钟

前言

Java 10 于 2018 年 3 月发布,是 Java 平台按照新的六个月发布周期发布的第一个版本。虽然相比 Java 8 和 Java 9 的大型更新,Java 10 的变化较小,但仍然引入了一些重要的特性,特别是本地变量类型推断(var)

1. 局部变量类型推断

1.1 概述

Java 10 最引人注目的特性是引入了 var 关键字,允许编译器根据变量初始化表达式自动推断局部变量的类型。这个特性使 Java 代码更加简洁,减少了冗余的类型声明。

值得注意的是,虽然 var 看起来像动态类型,但 Java 依然是强类型语言。var 只是在编译时由编译器负责类型推断,一旦推断完成,变量的类型就固定了,在运行时不会改变。

1.2 基本用法

// 传统方式
String greeting = "Hello, Java 10";

// 使用 var (Java 10)
var greeting = "Hello, Java 10";  // 编译器推断为 String 类型

1.3 适用范围

var 关键字只能用于以下场景:

  • 局部变量声明(有初始化器)
  • for 循环和增强 for 循环中的索引变量
  • try-with-resources 语句中的资源变量

1.4 不适用范围

var 关键字不能用于:

  • 方法参数
  • 构造函数参数
  • 方法返回类型
  • 字段
  • 捕获表达式(catch)
  • 没有初始化器的变量声明

1.5 实际使用示例

1.5.1 基本数据类型

var count = 10;          // 推断为 int
var price = 19.99;       // 推断为 double
var isValid = true;      // 推断为 boolean
var ch = 'A';            // 推断为 char

1.5.2 集合和泛型

// 简化集合声明
var names = new ArrayList<String>();
var employees = Map.of(
    1"张三",
    2"李四",
    3"王五"
);

// 遍历变得更简洁
for (var entry : employees.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

1.5.3 减少冗长的类型声明

// 传统方式 - 类型声明重复且冗长
HashMap<IntegerList<String>> dataMap = new HashMap<IntegerList<String>>();

// 使用 var - 更加简洁
var dataMap = new HashMap<IntegerList<String>>();

1.5.4 链式操作和临时变量

// 处理 JSON 数据
var jsonObj = new JSONObject(jsonString);
var userObj = jsonObj.getJSONObject("user");
var userName = userObj.getString("name");

// 文件操作
try (var inputStream = new FileInputStream("data.txt");
     var reader = new BufferedReader(new InputStreamReader(inputStream))) {
    var line = reader.readLine();
    // 处理文件内容
}

1.6 最佳实践与注意事项

1.6.1 何时使用 var

  • 当变量类型明显或不重要时
  • 当表达式右侧已经明确表示类型时
  • 当需要减少冗余代码时
var future = executor.submit(this::processData);  // 类型 Future<?> 不重要
var stream = list.stream().filter(Objects::nonNull);  // 类型明显

1.6.2 何时避免使用 var

  • 当需要明确类型作为文档时
  • 当使用菱形操作符时(避免双重推断)
  • 当会降低代码可读性时
// 不推荐 - 难以理解返回类型
var result = getValue();

// 不推荐 - 双重类型推断
var list = new ArrayList<>();  // 元素类型不明确

1.6.3 避免过度使用

过度使用 var 可能会使代码难以阅读和理解:

// 不好的实践 - 滥用 var
var x = getX();
var y = process(x);
var z = y.transform();

// 更好的实践 - 关键处保留类型信息
ResultType result = getX();
var processed = process(result);
OutputType output = processed.transform();

1.6.4 与其他语言的 var 对比

Java 的 var 与其他语言中的类似功能有重要区别:

语言关键字类型系统注意事项
Javavar静态类型,编译时推断只用于局部变量
JavaScriptvar/let动态类型变量类型可以改变
Kotlin无关键字静态类型,类型推断更广泛可用于属性、函数返回等
C#var静态类型,编译时推断使用更广泛
Scalaval/var静态类型,强大的类型推断val 不可变,var 可变

1.6.5 性能影响

var 关键字是纯编译时特性,对运行时性能没有任何影响。编译后的字节码与手动指定类型完全相同。

// 这两段代码生成的字节码完全一样
String name = "Java";
var name = "Java";

2. 垃圾收集器改进

2.1 G1 垃圾收集器的并行 Full GC (JEP 307)

Java 10 之前,当 G1 垃圾收集器触发 Full GC 时会使用单线程执行标记-清理-压缩算法。Java 10 使 G1 的 Full GC 也能并行化,大大减少了停顿时间。

2.1.1 背景和动机

G1 垃圾收集器旨在避免 Full GC,但在某些情况下(如内存压力大、并发收集无法足够快地回收内存),仍然会触发 Full GC。在 Java 9 及之前版本中,这种 Full GC 是单线程的,会导致显著的停顿时间。

2.1.2 实际配置

# 启用 G1 收集器(Java 9+ 默认)
java -XX:+UseG1GC -XX:ParallelGCThreads=4 -jar application.jar

2.2 应用程序类数据共享

2.2.1 概述

CDS(Class-Data Sharing)功能扩展到支持应用程序类,不再仅限于系统类。这可以:

  • 减少启动时间
  • 减少内存占用
  • 多个 JVM 间共享类元数据

应用程序类数据共享(AppCDS)是 Java 10 引入的一项重要性能优化技术,它通过允许多个 Java 进程共享类元数据来提高应用程序的启动性能和降低内存占用。

2.2.2 AppCDS 工作原理

AppCDS 的工作原理如下:

  1. 类归档创建:首先运行应用程序并记录加载的类
  2. 存储在共享归档中:将这些类的处理后元数据存储到归档文件中
  3. 内存映射加载:后续启动时,直接从归档文件内存映射这些类数据
  4. 跳过处理步骤:避免了类加载、解析和验证的开销

这一过程显著优化了以下几个方面:

  • 类加载时间:归档的类不需要重新解析和验证
  • 内存利用:多个 JVM 实例可以共享同一个类数据内存区域
  • 堆内存效率:减少了类元数据对 Java 堆的压力

2.2.3 实际应用示例

# 第一步:创建应用类列表
java -XX:+UseAppCDS -XX:DumpLoadedClassList=classes.lst -jar myapp.jar

# 第二步:创建共享归档文件
java -XX:+UseAppCDS -XX:SharedClassListFile=classes.lst -XX:SharedArchiveFile=myapp.jsa -jar myapp.jar

# 第三步:使用共享归档文件运行应用
java -XX:+UseAppCDS -XX:SharedArchiveFile=myapp.jsa -jar myapp.jar

3. 总结

Java 10 虽然是一个相对较小的更新,但带来了一些重要的改进,特别是本地变量类型推断(var)这一备受欢迎的语言特性。