Joda对象为什么是不可变的?

15

我读到过,使用低于7版的Java时,Joda Time对象比Java内置对象更可靠。其中一个原因是Joda对象是不可变的。为什么这样有益呢?如果我想要更改Joda DateTime对象的年份、小时和时区,我需要制作三份副本!


1
相反地,如果你的DateTime对象不是不可变的,每次将其作为参数传递给某个第三方API时,你应该克隆它...鉴于Java没有const - Dilum Ranatunga
5个回答

19

如果我想要更改Joda DateTime对象的年份、小时和时区,我需要创建三个副本!

确实如此。或者你可以创建一个新对象,使用旧对象中喜欢的所有字段。

这是非常好的方法,因为它意味着当你想要依赖于一个不变的对象时,它就不会改变。考虑下面的伪代码:

private static final Instant EARLIEST_ALLOWED_ARTICLE = ...;

private Instant creationTimestamp;

public Article(Instant creationTimestamp, ...) {
    if (creationTimestamp.isBefore(EARLIEST_ALLOWED_ARTICLE)) {
        throw new IllegalArgumetnException(...);
    }
    this.creationTimestamp = creationTimestamp;
    ...
}

这没问题,因为 Instant 是不可变的。如果它是可变的,在构造函数中的验证就没有意义了——除非你在那个时候创建了一个防御性副本。

根据我的经验,你想要传递引用并知道值不会更改,而不是实际上更改现有对象,所以不可变性会导致更少的错误(对于可变对象,你可能会忘记拷贝,并且任何验证都将无用),并且产生更少的副本。

基本上,它允许您在不制作接受和存储或返回给其他代码的所有内容的拷贝的情况下,在本地推理您的代码。当只有您的代码可以改变对象的状态时,更容易推断随时间发生的事情。

Joda Time 实际上有些失败,因为它既有可变的又有不可变的类型——如果你只是根据接口编程(例如ReadableInstant),那么你就无法得到这些保证。这就是为什么在Noda Time中我使所有类型都是真正的不可变的原因。

顺便问一下,您希望String是可变的吗?如果不是,请尝试考虑这两种情况之间是否存在真正的区别。


这些是非常好的观点。但是日期真的像不可变的“原始类型”(数字和字符串)一样特殊吗?您所描述的引出了一个问题,为什么我们不应该使所有类都是可变的,为什么不是每个setter ever在更改值之前都进行复制。在您编写的每个setter中编写clonecopy并不真正有意义。这暗示了一个根本性的问题。 - caw
1
@caw:是的,我会说日期确实是很特别的东西。有可变类型肯定是方便的,尽管在可行的情况下我通常更喜欢不可变类型。但如果所有低级“原始”类型都是可变的,那么可变性或不可变性的选择将更难以实现…不可变类将不得不一直克隆所有内容。这太糟糕了。 - Jon Skeet
谢谢!我还没有真正看到日期和其他常见对象(例如集合、Calendar、业务逻辑中的对象)之间的主要区别。无论如何,对于那些不打算稍后更改的对象(因此可能根本没有任何公共设置器),存在实质性差异,您只需要在构造函数/设置器中克隆接收到的数据(就像您上面的Article示例一样,这也是Joshua Bloch在“Effective Java”中推荐的方法),而那些打算更改的对象则需要在每个设置器中复制自己。 - caw

3

最简单的答案是,一旦你创建一个对象,你就知道它不能改变。这意味着你不会遇到数据在意料之外的方式下发生变化的情况(比如在代码的其他部分或不同的线程中)。

这使得对象的行为更加可预测和可靠。


1

可能有很多原因,但其中一个好的原因是哈希。不可变对象可以用于哈希数据结构(例如 HashSet,HashMap 中的键等),因为它们的哈希码和“相等性”语义不会改变。可变对象不适合用于哈希,因为变化可能会改变它们的哈希码或相等性。

通过使 Joda 日期成为不可变对象,现在可以将其用于哈希数据结构中。


1
我认为这是由于以下原因:
假设您有一个java.util.Date对象,该对象在多个类中使用。我正在开发一个类,而您正在开发另一个类。然后,我决定需要预测未来,但我没有创建新的Date对象,而是使用了已经存在的那个,即您认为代表某个时间点的那个对象,并将其加上三个小时。现在,您的代码可能会在运行时遇到严重问题。所有这些都可以通过使用不可变对象来避免,因为您无法更改其状态,也无法为其他人搞砸它。

1

我不明白。DateTime是一个“final”类,如果你指的是BaseDateTime中的字段,那么是的,它们不是“final”,可以使用反射进行修改,但这样做的话,“String”也不是不可变的。 - Premraj

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