返回私有数据的副本而不是引用

时间:2014-09-06 20:13:27

标签: java reference

在令人惊讶的书“Java the Good Parts”中,作者给出了一些代码,这些代码在其getter方法中返回一个对象的副本(与一个封装良好的字段一起使用)然后声明:

  

此方法尝试返回私有数据的副本   而不是对私人数据的引用,通常是一个好主意

为什么会这样?我认为封装的目标是确保没有人能够真正改变私人成员。那我为什么要写这样的东西

private someType fieldName = new someType(); 

...

同时定义它的getter(假设存在某种复制构造函数)

someType getSomething() 
{
return new someType(fieldName); 
}

根据我现在所知,我的意思是在你们弹出之前是:

到目前为止,这是有意义的,它服务垃圾收集,因为这种方法不维护对实际对象的引用。

从内部类的角度来看也是可以理解的,任何方法都可以通过引用来改变任何可访问的字段。

但我并不怀疑这样做的两个原因是什么才真正超越了这个问题。

4 个答案:

答案 0 :(得分:19)

当类型可变时,常常对返回副本很有用,这样客户端就无法从根本上修改数据,至少在没有告诉你的情况下。考虑:

public class Person {
    private Date dateOfBirth;

    public Date getDateOfBirth() {
        return dateOfBirth;
    }

    public void setDateOfBirth(Date dateOfBirth) {
        // Do some validation, e.g. that it's after 1800
        this.dateOfBirth = dateOfBirth;
    }
}

看起来没问题吧?但是怎么样:

Person person = new Person();
person.setDateOfBirth(new Date()); // Now... fine.
// Aha! Modify the Date to a very long time ago. Now anyone else
// using the Person will be messed up...
person.getDateOfBirth().setTime(Long.MIN_VALUE);

如果getDateOfBirth返回副本,则调用者对返回值引用的Date对象所做的任何更改都将与其他任何人无关。 Person对象仍然有效,因为它只有一个有效的日期。当然,这应该记录下来,以便编写上述代码的人期望它不会影响Person对象,因为返回了一个副本。

更好的解决方案比所有这些复制都支持不可变类型,但是当你有一个不可变对象的引用时,你可以随意分享它,知道没有人可以改变你脚下的状态。

答案 1 :(得分:7)

我们的想法是,getter允许您查看对象的状态而无法对其进行修改(因为您将修改副本而不是原始文件)。

如果你打电话:

someType property = someObj.getSomething();

然后

property.setSomeSubProperty(someValue);

这只会更改someType的副本,而不会更改someType中存储的原始someObj

如果包含getSomething()方法的类是可变的,则它可能具有setSomething(someType value)方法,并且使用该方法将是修改该属性的可接受方法。

答案 2 :(得分:2)

已经提出了很好的答案,但让我想出其他的例子和参考资料

解释的最佳来源是Josh Bloch的 Effective Java 。关于不变性和防御性副本至少有两章。

简而言之: 在Java中,您通过引用传递所有内容(我知道它过于简单,但这不是重点)并且许多类都是可变的。因此,私有字段与外部对象的直接分配并不十分安全,因为下面的值可以在任何时间点从对象外部改变,从而打破封装。

访问器方法是打破封装的本质。在最常见的实现中,您只需将该字段设置为public,如上所述,您允许任何人更改底层对象(如果允许的话)。最好的例子是集合恕我直言。如果您返回任何默认Java集合,任何人都可以添加内容,删除元素甚至清除它。如果你的逻辑取决于状态,或者你正在编写多线程应用程序,那么这是获得竞争条件的最简单方法,这是我们真正不想要的。

所以好的做法是

  • 返回对象的深层副本(例如Guava副本收集方法)
  • 返回对象的视图(例如Collections类及其方法)
  • 使用不可变对象(最简单的'em all)
  • 克隆或其他时髦的生意

每个人都有一些与他们相关的费用。复制/克隆需要时间和内存。视图不是完全安全的,因为底层实现可能在任何时间点发生变化,不可变对象不允许修改,并且难以在遗留系统中实现等等。由您来寻找平衡,但我们总是很乐意提供帮助: )

最后一点,在传递可变参数的构造函数/ setter中执行防御性复制也是一种很好的做法,原因完全相同。如果有人将元素添加到我们在构造函数中最终得到的集合中,那将是非常愚蠢的,因为我们不保留状态,这是我们显然想要的。所以,再次,在构造函数中,如果你没有控制传入的内容,就不要做一个简单的初始化(即使你这样做,也可能是复制的好主意)

我更喜欢集合作为一个例子,因为它们更容易推理如何复制/如何更改,但是在其他答案中提到的StringBuilder和Date,确实表明它不仅仅是集合的问题。所以最好的答案是:记住,final是你最好的朋友。从一开始就经常使用它,永远不要相信可变的陌生人!

答案 3 :(得分:0)

由于没有“私人数据”这样的东西,实际上只有您无法访问的数据和无法更改的数据。

假设fieldName被定义为StringBuilder fieldName。您无法对StringBuilder做任何事情,以防止有权访问它的人修改它。另一方面,如果它被定义为字符串fieldName,那么(没有一些真正邪恶的反映)就没有其他人改变它的可能性。

因此,它是不可变性的昂贵替代品。更好的是使用仅允许访问您想要提供的属性和操作的包装器。