编程Java开发者

Java中的不可变对象是什么,它们的价值何在,以及如何正确实现自己的不可变类?

用 Hintsage AI 助手通过面试

答案。

不可变对象是指在创建后无法改变其状态的对象。它们的主要特性包括:

  • 所有字段为final;
  • 不包含setter;
  • 对内部对象(例如集合)不能获得可变引用。

不可变对象的优点:

  • 对于多线程访问是安全的(线程安全);
  • 容易缓存并在集合中重用作为键;
  • 简化调试和测试;
  • 由于状态的意外变化导致的错误更少。

不可变类的实现示例:

public final class Person { private final String name; private final int age; private final List<String> phones; public Person(String name, int age, List<String> phones) { this.name = name; this.age = age; // 保护传入的列表不被修改 this.phones = Collections.unmodifiableList(new ArrayList<>(phones)); } public String getName() { return name; } public int getAge() { return age; } public List<String> getPhones() { return phones; } // 返回只读列表 }

有陷阱的问题。

为什么Java中的String是不可变的,如果不是这样会发生什么?很多人回答“为了安全”,但这在实践中意味着什么?

答案:

String被广泛使用:作为集合中的键,在安全逻辑中(例如,密码)。如果可以通过一个引用改变字符串,这将影响所有指向同一对象的其他引用,从而使集合的正确工作变得不可能(例如,在计算hashCode时的HashMap),并可能导致安全漏洞。

因不知道主题细节而导致的实际错误示例。


故事

在一个大型银行项目中,通过普通的getter传递内部集合(交易列表)。结果,列表可以外部修改,破坏了不变性(例如,添加不正确日期的交易)。这导致了数据丢失,直到开始返回Collections.unmodifiableList

故事

在根配置类中存储了不受保护的字段对象(DateList)。在一个线程中修改了配置,而在另一个线程中获取过时或不一致的数据,这导致业务算法错误触发。

故事

在登录系统中,密码存储在可变对象中。由于不安全的访问,另一个用户的密码意外“泄露”,因为同一个对象实例被多个线程使用。