1. 概述

本教程将展示如何使用双大括号在单个Java表达式中创建并初始化对象。我们也会分析为什么这种技术被视为反模式

2. 标准实现方式

通常我们初始化并填充国家集合时,会这样写:

@Test
public void whenInitializeSetWithoutDoubleBraces_containsElements() {
    Set<String> countries = new HashSet<String>();                
    countries.add("India");
    countries.add("USSR");
    countries.add("USA");
 
    assertTrue(countries.contains("India"));
}

从上述代码可以看出,我们执行了三个步骤:

  1. 创建 HashSet 实例
  2. HashSet 添加国家
  3. 最后验证国家是否存在于 HashSet

3. 使用双大括号

实际上,我们可以将创建和初始化合并到单个语句中——这就是双大括号的应用场景:

@Test
public void whenInitializeSetWithDoubleBraces_containsElements() {
    Set<String> countries = new HashSet<String>() {
        {
           add("India");
           add("USSR");
           add("USA");
        }
    };
 
    assertTrue(countries.contains("India"));
}

这段代码的实际执行过程:

  1. 创建一个继承自 HashSet 的匿名内部类
  2. 在实例初始化块中调用 add 方法添加国家名称
  3. 最后验证国家是否存在于 HashSet

4. 双大括号的优势

使用双大括号有几个简单优势: ✅ 相比原生方式代码行数更少
✅ 代码可读性更高
✅ 创建和初始化在同一表达式中完成

5. 双大括号的劣势

但双大括号存在明显缺陷: ❌ 初始化方式晦涩难懂,非主流用法
❌ 每次使用都会创建额外匿名类
❌ 不支持Java 7引入的“菱形运算符”
❌ 当目标类被标记为 final 时无法使用
❌ 持有对外部实例的隐藏引用,可能导致内存泄漏

正是这些缺陷导致双大括号初始化被视为反模式。

6. 替代方案

6.1 Stream工厂方法

我们可以改用Java 8的Stream API初始化集合:

@Test
public void whenInitializeUnmodifiableSetWithDoubleBrace_containsElements() {
    Set<String> countries = Stream.of("India", "USSR", "USA")
      .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));
 
    assertTrue(countries.contains("India"));
}

6.2 Java 9集合工厂方法

Java 9提供了更简洁的工厂方法:

List<String> list = List.of("India", "USSR", "USA");
Set<String> set = Set.of("India", "USSR", "USA");

更多细节可参考这篇文章

7. 总结

本教程探讨了双大括号初始化的使用方式及其优缺点。所有示例代码可在GitHub项目中找到——这是一个基于Maven的项目,可直接导入运行。


原始标题:Java Double Brace Initialization