特别是在Java中,但也可能在其他语言中:对同一对象有两个引用何时有用?
例:
Dog a = new Dog();
Dob b = a;
在这种情况下有用吗?a
当您想与所代表的对象进行交互时,为什么这是首选的解决方案a
?
a
并且b
始终引用相同Dog
,则没有任何意义。如果他们有时会这样做,那么它很有用。
特别是在Java中,但也可能在其他语言中:对同一对象有两个引用何时有用?
例:
Dog a = new Dog();
Dob b = a;
在这种情况下有用吗?a
当您想与所代表的对象进行交互时,为什么这是首选的解决方案a
?
a
并且b
始终引用相同Dog
,则没有任何意义。如果他们有时会这样做,那么它很有用。
Answers:
例如,当您想在两个单独的列表中包含相同的对象时:
Dog myDog = new Dog();
List dogsWithRabies = new ArrayList();
List dogsThatCanPlayPiano = new ArrayList();
dogsWithRabies.add(myDog);
dogsThatCanPlayPiano.add(myDog);
// Now each List has a reference to the same dog
另一个用途是当同一对象扮演多个角色时:
Person p = new Person("Bruce Wayne");
Person batman = p;
Person ceoOfWayneIndustries = p;
Persona batman = new Persona("Batman"); Persona bruce = new Persona("Bruce Wayne"); Persona currentPersona = batman;
-您具有多个可能的值(或可用值的列表)以及对当前活动/选定的值的引用。
currentPersona
指向一个对象或另一个,但不能同时指向两个对象。特别是,它很容易被可能currentPersona
被永远不会设置为bruce
,在这种情况下,它肯定不是两个对象的引用。在我的示例中,我会说batman
和currentPersona
都是对同一实例的引用,但是在程序中具有不同的语义。
这实际上是一个令人惊讶的深刻问题!现代C ++的经验(以及从现代C ++汲取的语言,例如Rust)表明,您通常不希望这样做!对于大多数的数据,你想有一个单一的或独特的(“拥有”)的参考。这也是线性系统的主要原因之一。
但是,即使那样,您通常也希望使用一些短暂的“借用”引用来短暂访问内存,但不会在数据存在的大部分时间内持续使用。最常见的情况是将对象作为参数传递给其他函数(参数也是变量!):
void encounter(Dog a) {
hissAt(a);
}
void hissAt(Dog b) {
// ...
}
不常见的情况是,您根据条件使用两个对象之一,而实际上无论选择哪种对象都做相同的事情:
Dog a, b;
Dog goodBoy = whoseAGoodBoy ? a : b;
feed(goodBoy);
walk(goodBoy);
pet(goodBoy);
回到更常见的用途,但保留局部变量,我们转到字段:例如,GUI框架中的小部件通常具有父级小部件,因此,包含十个按钮的大框架将至少具有十个指向它的引用(以及更多其他内容)。从它的父母可能是从事件侦听器等)。任何种类的对象图和某些种类的对象树(带有父/兄弟引用的对象树)都有多个对象引用每个相同的对象。实际上,每个数据集实际上都是一个图形;-)
临时变量:考虑以下伪代码。
Object getMaximum(Collection objects) {
Object max = null;
for (Object candidate IN objects) {
if ((max is null) OR (candidate > max)) {
max = candidate;
}
}
return max;
}
变量max
和candidate
可能指向同一对象,但是变量分配使用不同的规则和在不同的时间更改。
当您有多个对象都可以回调到另一个可以无上下文使用的对象时,此方法非常有用。
例如,如果您具有选项卡式界面,则可能具有Tab1,Tab2和Tab3。您可能还希望能够使用公共变量,而不管用户位于哪个选项卡上,以简化代码并减少在运行过程中以及用户位于哪个选项卡上时一目了然的情况。
Tab Tab1 = new Tab();
Tab Tab2 = new Tab();
Tab Tab3 = new Tab();
Tab CurrentTab = new Tab();
然后,在每个编号的选项卡onClick中,您可以更改CurrentTab以引用该选项卡。
CurrentTab = Tab3;
现在,在您的代码中,您可以不受惩罚地调用“ CurrentTab”,而无需知道您实际在哪个选项卡上。您还可以更新CurrentTab的属性,它们将自动向下流动到引用的选项卡。
在很多情况下,b
必须引用未知的“ a
”才能有用。特别是:
b
编译时指向什么。例如:
参量
public void DoSomething(Thing &t) {
}
t
是对外部作用域中变量的引用。
返回值和其他条件值
Thing a = Thing.Get("a");
Thing b = Thing.Get("b");
Thing biggerThing = Thing.max(a, b);
Thing z = a.IsMoreZThan(b) ? a : b;
biggerThing
和z
分别是a
或的引用b
。我们不知道在编译时哪个。
Lambda及其返回值
Thing t = someCollection.FirstOrDefault(x => x.whatever > 123);
x
是一个参数(上面的示例1),并且t
是一个返回值(上面的示例2)
馆藏
indexByName.add(t.name, t);
process(indexByName["some name"]);
index["some name"]
在很大程度上是更精致的外观b
。它是创建并填充到集合中的对象的别名。
循环
foreach (Thing t in things) {
/* `t` is a reference to a thing in a collection */
}
t
是对由迭代器(上一示例)返回的项目(示例2)的引用。
这很关键,但是恕我直言是值得理解的。
所有OO语言都始终创建引用的副本,并且永远不会“无形地”复制对象。如果OO语言以任何其他方式工作,编写程序将更加困难。例如,函数和方法永远无法更新对象。如果没有显着增加的复杂性,Java和大多数OO语言几乎是不可能使用的。
程序中的对象应该具有某些含义。例如,它代表了现实世界中特定的事物。通常,对同一事物有很多引用是有意义的。例如,我的家庭住址可以提供给许多人和组织,并且该住址总是指相同的地理位置。因此,第一点是,对象通常代表特定,真实或具体的事物;因此,能够对同一事物进行多次引用非常有用。否则将很难编写程序。
每次将您a
作为参数/参数传递给另一个函数(例如,调用
foo(Dog aDoggy);
或应用方法)时a
,基础程序代码都会复制该引用,以生成对同一对象的第二个引用。
此外,如果具有复制的引用的代码在不同的线程中,则可以同时使用这两者来访问同一对象。
因此,在大多数有用的程序中,将存在对同一对象的多个引用,因为这是大多数OO编程语言的语义。
现在,如果我们考虑一下,因为通过引用传递是许多OO语言(C ++支持这两种语言)中唯一可用的机制,因此我们可能希望它是“正确的” 默认行为。
恕我直言,使用引用是正确的default,原因有两个:
还有一个效率论证。复制整个对象的效率不及复制参考。但是,我认为这没有意义。对同一对象的多个引用更有意义,并且更易于使用,因为它们与现实世界的语义相匹配。
因此,恕我直言,通常有多个对同一个对象的引用是有意义的。在算法上下文中没有意义的异常情况下,大多数语言都提供了“克隆”或深拷贝的功能。但是,这不是默认值。
我认为有人认为这不应该是默认值,他们使用的语言不提供自动垃圾收集功能。例如,老式的C ++。问题在于,他们需要找到一种方法来收集“死”对象,而不是回收仍然需要的对象。对同一个对象有多个引用会很困难。
我认为,如果C ++具有足够低廉的垃圾收集成本,以便所有引用的对象都被垃圾收集,那么大部分异议就会消失。仍然会有某些情况下是参考语义并不需要什么。但是,以我的经验,可以识别这些情况的人通常也能够选择适当的语义。
我相信有证据表明,C ++程序中有大量代码可以处理或减轻垃圾收集。但是,编写和维护这种“基础结构”代码会增加成本。它可以使语言更易于使用或更强大。因此,例如Go语言的设计重点是补救C ++的某些弱点,除了垃圾回收之外别无选择。
在Java上下文中,这当然是无关紧要的。它也被设计为易于使用,垃圾收集也是如此。因此,具有多个引用是默认的语义,并且在存在引用的情况下不回收对象的意义上来说是相对安全的。当然,它们可能会被数据结构所保留,因为该程序在真正完成一个对象时无法正确整理。
因此,回过头来回答您的问题(有点概括),您何时会希望对同一个对象有多个引用?我几乎可以想到的每种情况。它们是大多数语言参数传递机制的默认语义。我建议这是因为处理现实世界中存在的对象的默认语义必须通过引用来实现(因为“实际对象在那儿”)。
任何其他语义将更难处理。
Dog a = new Dog("rover"); // initialise with name
DogList dl = new DogList()
dl.add(a)
...
a.setOwner("Mr Been")
我建议“漫游器” dl
应该是受其影响setOwner
或程序难以编写,理解,调试或修改的“漫游器” 。我认为大多数程序员否则会感到困惑或沮丧。
后来,狗被卖掉了:
soldDog = dl.lookupOwner("rover", "Mr Been")
soldDog.setOwner("Mr Mcgoo")
这种处理是常见且正常的。因此,引用语义是默认语义,因为它通常最有意义。
简介:对同一个对象有多个引用总是有意义的。
在Web应用程序中,对象关系映射器可以使用延迟加载,以便对同一数据库对象的所有引用(至少在同一线程内)指向同一事物。
例如,如果您有两个表:
如果进行以下调用,那么您的ORM可以采取几种方法:
dog = Dog.find(1) // fetch1
owner = Owner.find(1) // fetch2
superdog = owner.dogs.first() // fetch3
superdog.name = "Superdog"
superdog.save! // write1
owner = dog.owner // fetch4
owner.name = "Mark"
owner.save! // write2
dog.owner = Owner.find(2)
dog.save! // write3
在幼稚策略中,对模型和相关引用的所有调用都检索单独的对象。Dog.find()
,Owner.find()
,owner.dogs
,并且dog.owner
导致数据库打到第一次,然后将它们保存到内存中。所以:
如果没有参考,则将有更多的访存,使用更多的内存并引入了覆盖早期更新的可能性。
假设您的ORM知道对dogs表第1行的所有引用都应指向同一事物。然后:
换句话说,使用引用有助于将单个真理点(至少在该线程内)作为数据库中的行的原则进行整理。