在Java中创建不可变对象

3
我想为我的代码库创建几个不可变对象。那么,怎样能够确切地传达给其他人这个类是不可变的呢?我应该将所有字段设为final,并在对象构建时进行初始化吗?(这似乎非常笨拙...)还是应该创建一些Immutable接口,让对象实现它?(由于Java没有此背后的标准接口,我认为他们有其他方法来处理它。)在处理这个问题时,有什么标准的方式吗?(如果只是在字段周围添加一堆注释表明它们在初始化后不应进行修改,那也可以。)

1
你到底担心什么?是你的同事不理解并修改你的类使其可变吗? - Daniel Kaplan
是的,正是如此。我担心未来查看代码的人可能无法完全欣赏类背后的不可变设计,并可能会创建setter等。 - Sal
3个回答

8
我应该将所有字段都设为final并在对象构建时进行初始化吗?
是的。并确保这些类型本身是不可变的,或者在从getter方法返回值时创建副本。同时将类本身设置为final。(否则,您的类本身可能是不可变的,但这并不意味着您类的任何实例都是不可变的——因为它可能是可变子类的实例。)
(这似乎非常笨拙...)
不知道你为什么觉得这很笨拙,很难给出建议——但生成器模式通常很有用。我通常使用嵌套的静态类,通常还带有一个静态工厂方法。所以最终会得到:
Foo foo = Foo.newBuilder()
    .setName("asd")
    .setPoints(10)
    .setOtherThings("whatever")
    .build();

2
+1 表示“并确保这些类型本身是不可变的”。对于不可变接口或注释作为类似于 Serializable 接口的元数据,您有何看法? - Craig
1
@Craig 你想到的是 这个 - Louis Wasserman
谢谢Jon,这回答了我的问题。为了回答你的问题,我发现让每个字段都是final的方式很尴尬,因为在我看到的代码中通常不会这样做。 - Sal
2
如果您没有setter,那么您不必将它们设置为final,但这样做可以清楚地向代码读者传达意图,这是良好编程的核心。此外,字段不一定是不可变的 - 如果它们不是,则让getter返回一个副本即可。 - Bohemian
@Bohemian:没错。我总是倾向于将事物设为final,这不仅有助于可读性,还可以避免自己犯错误。 - Jon Skeet
1
你已经得到我的+1了 :) 我同意使用final,因为使用final来定义字段的代码信号噪声比高。而且每个人,包括作者在内,都被“保留”。我只是在限定答案。 - Bohemian

5
是和不是。仅将所有字段设置为final本身并不能保证不可变性。如果您想深入了解此问题,可以参考Joshua Bloch的《Effective Java》中涉及到的多个章节,其中第15条目涵盖了大部分内容并引用了其他相关条目。
他提供了以下五个步骤:
  1. 不提供任何修改对象状态的方法(称为mutators)。
  2. 确保该类无法被扩展。
  3. 将所有字段都设置为final。
  4. 将所有字段都设置为private。
  5. 确保对任何可变组件的独占访问。
学习如何实现这些步骤的一种方式是查看语言设计者如何通过查看像String这样的不可变类的源代码来使类成为不可变(例如,请参见http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/String.java)。

1
以下是Joshua Bloch在《Effective Java》中关于第五条建议的内容:“如果你的类有任何引用可变对象的字段,请确保类的客户端无法获取对这些对象的引用。永远不要将这样的字段初始化为客户端提供的对象引用,也不要从访问器返回对象引用。” - andreih
1
这也被称为制作“防御性副本”。 - Andy Thomas

1

编写一个单元测试,如果你的同事使类可变,则该测试将失败。

使用 Mutability Detector,您可以编写以下测试:

import static org.mutabilitydetector.unittesting.MutabilityAssert.assertImmutable;

@Test public void isImmutable() {
    assertImmutable(MyImmutableThing.class)
}

如果一个同事来了,例如给你的类添加了一个setter方法,测试将会失败。你的用例是可变性检测器的核心目的之一。
免责声明:我写过这个工具。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接