别名错误

2016年11月14日

当同一个内存位置通过多个引用访问时,就会发生别名问题。这通常是件好事,但经常以意想不到的方式发生,从而导致令人困惑的错误。

这是一个简单的错误示例。

Date retirementDate = new Date(Date.parse("Tue 1 Nov 2016"));

// this means we need a retirement party
Date partyDate = retirementDate;

// but that date is a Tuesday, let's party on the weekend
partyDate.setDate(5);

assertEquals(new Date(Date.parse("Sat 5 Nov 2016")), retirementDate);
// oops, now I have to work three more days :-(

这里发生的事情是,当我们进行赋值时,partyDate 变量被分配了一个指向与 retirementData 相同对象的引用。如果我随后更改该对象的内部内容(使用 setDate),那么这两个变量都会更新,因为它们指向同一事物。

虽然别名在那个例子中是一个问题,但在其他情况下,它是我所期望的。

Person me = new Person("Martin");
me.setPhoneNumber("1234");
Person articleAuthor = me;
me.setPhoneNumber("999");
assertEquals("999", articleAuthor.getPhoneNumber());

通常希望共享这样的记录,然后如果它发生变化,它会对所有引用发生变化。这就是为什么考虑引用对象很有用,我们故意共享它们[1],以及值对象,我们不希望这种共享更新行为。避免值对象共享更新的一个好方法是使值对象不可变。

当然,函数式语言更喜欢所有东西都是不可变的。因此,如果我们希望更改被共享,我们需要将其作为例外而不是规则来处理。不可变性是一个方便的属性,它使创建几种类型的错误变得更加困难。但是,当确实需要更改时,不可变性会引入复杂性,因此它绝不是免费的午餐。

致谢

格雷厄姆·布鲁克斯和詹姆斯·伯尼在我们内部邮件列表上的评论促使我写了这篇文章。

进一步阅读

术语“别名错误”已经存在一段时间了。它出现在埃里克·雷蒙德的术语文件中,在 C 语言的上下文中,原始内存访问使其更加令人不快。

笔记

1: 埃文斯分类 有实体的概念,我认为这是一种常见的引用对象形式。