欢迎访问shiker.tech

请允许在我们的网站上展示广告

您似乎使用了广告拦截器,请关闭广告拦截器。我们的网站依靠广告获取资金。

订阅shiker.tech

文章发布订阅~

通过邮箱订阅文章更新,您将在文章发布时收到及时的邮件提醒~

Java如何优雅地创建不可变集合?
(last modified Apr 20, 2025, 8:14 PM )
by
侧边栏壁纸
  • 累计撰写 217 篇文章
  • 累计创建 70 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

Java如何优雅地创建不可变集合?

橙序员
2025-04-20 / 0 评论 / 0 点赞 / 366 阅读 / 2,288 字 / 正在检测百度是否收录... 正在检测必应是否收录...
文章摘要(AI生成)

Java 在集合创建方面常被批评语法冗长,尤其是构建不可变集合时。传统方法需要多步操作,易导致代码冗余和出错,如遗漏不变性包装步骤。而 Java 8 的流和收集器简化了集合操作,但其使用较为复杂,并可能引入额外的内存消耗。Java 9 引入的 Set.of()、List.of() 和 Map.of() 工厂方法提供了一种更为简洁且高效的创建不可变集合的解决方案。这些方法不仅可以在一行代码中创建集合,还通过优化性能,尤其是小集合的内存占用。这些工厂方法创建的集合具有不可变性,避免数据被意外修改的风险,但对元素的空值有严格限制,同时不保证元素的插入顺序。因此,对于 Java 开发者而言,使用 Java 9 及以上版本的工厂方法是创建不可变集合的推荐方式,能够显著提升代码的可读性和开发效率。

Java 一直以来都被诟病语法冗长,尤其在构造简单集合时尤为明显。从基础的数据存储到复杂的业务逻辑实现,集合的使用频率极高,而创建集合的方式直接影响着代码的可读性与开发效率。本文将带你深入对比各种创建不可变集合方式的利弊,为你介绍更现代、高效的解决方案,助你在 Java 开发中写出优雅且安全的代码。

🧩 问题引入:Java 创建集合为何如此麻烦?

普通构造 + add + unmodifiable

为了构造一个不可变集合,需要先构造、再添加、最后包装。这对只包含几个元素的小集合来说,显得冗长且不直观。

Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set.add("c");
set = Collections.unmodifiableSet(set);

这种方式虽然逻辑清晰,最终能得到不可变集合,但构建过程过于繁琐。在实际项目中,如果频繁创建类似的小集合,会导致代码冗余度高,降低开发效率。而且,一旦开发人员遗漏了最后的 Collections.unmodifiableSet 包装步骤,原本期望的不可变集合就会变得可变,从而引发数据安全问题。

new HashSet<>(Arrays.asList())

该方法通过将数组转换为 List,再利用 HashSet 的构造函数创建集合,看似简洁了一些,但实际上引入了不必要的中间结构。Arrays.asList 方法返回的是一个固定大小的 List,其底层实现与常规的 ArrayList 有所不同,这种差异在阅读和理解代码时会造成一定困扰。同时,过多的中间转换过程也会影响代码的执行效率。

双括号初始化 {{ }}

双括号初始化语法通过创建一个匿名内部类来简化集合的创建过程,例如:

Set<String> set = new HashSet<>() {{
    add("a");
    add("b");
    add("c");
}};

虽然这种方式在语法上简洁明了,但它存在诸多风险。首先,每次使用双括号初始化都会创建一个新的匿名内部类,这会导致内存占用增加,若在循环或频繁调用的方法中使用,可能会引发内存泄漏问题。其次,匿名内部类在序列化时也可能出现兼容性问题,给项目带来潜在风险。

Java 8 Stream + Collectors

Java 8 引入的流(Stream)和收集器(Collectors)为集合操作提供了强大的功能。通过 Stream.of 方法创建流,再使用 collect(toSet()) 方法将流中的元素收集为集合,最后通过 Collections.unmodifiableSet 包装为不可变集合:

Set<String> set = Stream.of("a", "b", "c")
                        .collect(Collectors.toSet());
set = Collections.unmodifiableSet(set);

这种方式虽然在一定程度上简化了代码,但流操作会创建额外的对象,增加内存开销。而且,对于不熟悉流操作的开发者来说,代码的语义不够直观,理解和调试起来相对困难。

Java 9 Set.of() 工厂方法

Java 9 推出的 Set.of()List.of()Map.of() 等工厂方法,为创建不可变集合提供了一种简洁高效的解决方案。以 Set.of()为例:

Set<String> set = Set.of("a", "b", "c");

只需一行代码,就能创建一个不可变的 Set,既简洁又直观。同时,JDK 针对小集合对这些工厂方法进行了优化,例如共享内部结构,提高了性能。不过,该方法要求 JDK 版本在 9 及以上,如果项目还在使用较低版本的 JDK,则无法享受这一便利。

方式对比

方法 简洁性 可读性 是否不可变 潜在问题
普通构造 + add + unmodifiable 冗长,容易出错
new HashSet<>(Arrays.asList()) 中间结构复杂,阅读困难
双括号初始化 {{ }} 内存泄漏、额外类、序列化风险
Java 8 Stream + Collectors ⚠️ ✅(需手动) 创建额外对象,语义不直观
Java 9 Set.of() 工厂方法 JDK 9 及以上版本支持

📊 图示:创建不可变集合的演进

创建不可变集合的方式,我们经历了以下路程:

image-1745151157655

💡 扩展用法:除了 Set,还有 ListMap

✅ Java 9 工厂方法

// List
List<String> list = List.of("a", "b", "c");

// Set
Set<String> set = Set.of("a", "b", "c");

// Map(最多10对键值)
Map<String, Integer> map = Map.of("a", 1, "b", 2, "c", 3);

// Map 多于10个元素,使用 Map.ofEntries
Map<String, Integer> map2 = Map.ofEntries(
    Map.entry("a", 1),
    Map.entry("b", 2),
    Map.entry("c", 3),
    Map.entry("d", 4)
);

🔍 工厂创建的特点

  • 不可变:这些工厂方法创建的集合均为不可变集合,一旦尝试添加或修改元素,就会抛出 UnsupportedOperationException 异常,有效避免了数据被意外修改的风险,确保了数据的安全性和一致性。

  • 元素不能为空值:无论是作为 Mapkey 还是 valuenull 都会引发NullPointerException异常。这一限制促使开发者在使用时更加严谨地处理数据,避免因空指针问题导致程序崩溃。

  • 顺序固定(不保证插入顺序):由于底层实现可能并非 LinkedHashMap LinkedHashSet,这些集合并不保证元素的插入顺序。在对元素顺序有严格要求的场景下,开发者需要谨慎选择,或者采用其他合适的数据结构。

  • 性能优秀:JDK 针对小集合对工厂方法进行了专门优化,例如共享内部结构,减少了内存占用和对象创建开销,使得在创建小集合时具有出色的性能表现。

🧪 快速对比代码段(List、Set、Map)

类型 JDK 8 及以下 JDK 9+ 推荐方式
List Arrays.asList("a", "b") + wrap List.of("a", "b")
Set new HashSet<>(Arrays.asList(...)) Set.of("a", "b")
Map Map<String, String> m = new HashMap<>(); m.put(...) Map.of("a", 1, "b", 2)

🚫 小心这些坑!

在使用 Java 9 的工厂方法创建不可变集合时,有几个常见的陷阱需要特别注意:

  • Set.of("a", "b", "a") → 会抛 IllegalArgumentException(元素重复):因为 Set 中不允许存在重复元素,使用 Set.of() 方法创建 Set 时,若传入重复元素,会立即抛出异常。

  • Map.of(null, 1) → 会抛 NullPointerException(键或值为 null):如前文所述,这些工厂方法创建的集合不允许元素为 null,无论是键还是值,否则会引发空指针异常。

  • 不支持添加或删除操作,否则抛异常:由于集合是不可变的,任何试图添加或删除元素的操作都会导致 UnsupportedOperationException 异常,开发者在编写代码时务必牢记这一点。

  • 最多 10 个键值对,超过需使用 Map.ofEntries(...)Map.of()方法最多只能接受 10 对键值对,若需要创建包含更多元素的 Map,则必须使用 Map.ofEntries(...) 方法,否则会导致编译错误。


🔚 总结

Java 的集合初始化方案历经多年发展,已经从繁琐的传统写法逐步演进为简洁高效的现代方式。尽管目前 Java 语言层面尚未支持集合字面量,但 JDK 9 提供的 of() 工厂方法无疑是一大亮点。它让开发者在构造小而美的不可变集合时,无需再为冗长的代码和潜在的安全问题担忧,真正实现了可读性、安全性和效率的完美平衡

✅ 建议使用场景

场景 推荐写法
初始化只读配置列表 List.of(...)
构造常量型权限名集合 Set.of(...)
简单映射关系(最多10个) Map.of(...) / Map.ofEntries(...)

在实际项目开发中,合理运用这些推荐写法,能够让代码更加简洁优雅,提升项目的整体质量和可维护性。希望本文的内容能帮助你在 Java 集合操作中更加得心应手,写出更优质的代码。

0

评论区