Java: For-Each循环和引用

17
我想知道下面的循环代码是否会创建对象的副本,而不是给它一个引用。原因是第一个示例没有分配我的数组对象,但第二个示例却有。
MyObject objects[] = new MyObject[6];
for (MyObject o: objects) {

    o = new MyObject();
}

MyObject objects[] = new MyObject[6];
for(int i = 0; i < objects.length; i++) {

    objects[i] = new MyObject();
}

Java 中是否真的存在像第一个那样的 for 循环语法? - Chandra Sekhar
@ChandraSekhar: Java中的for-each循环 - RanRag
@ChandraSekhar RanRag是正确的。然而,这与问题无关。也许OP只是重新编写了他的代码,而不是复制/粘贴,因此在他喜欢的语言中思考。 - Ryan Amos
@RanRag 根据您的第一条评论,应该有一个冒号(:),但我困惑的是关键词“in”。它从哪里来? - Chandra Sekhar
@ChandraSekhar:抱歉,我以为你在想for-each循环。 - RanRag
7个回答

15

Java的工作方式与许多其他语言略有不同。在第一个示例中,o只是指向对象的引用。

当你说o = new MyObject()时,它会创建一个类型为MyObject的新对象,并将o引用到该对象,而之前o引用了objects[index]

也就是说,objects[index]本身只是指向内存中另一个对象的引用。因此,要将objects[index]设置为一个新的MyObject,则需要更改objects[index]指向的位置,这只能通过使用objects[index]来完成。

图像:(我的绘画技巧很糟糕:D)

enter image description here

说明: 这大致是Java内存管理的工作方式。绝非完全如此,但大致如此。您有对象(Object),它们引用A1。当您访问对象数组时,从开始的引用点(A1)开始,向前移动X个块。例如,引用索引1会将您带到B1。 B1然后告诉您正在寻找位于A2上的对象。 A2告诉您它具有位于C2上的字段。 C2是一个整数,是基本数据类型。搜索完成。

o不引用A1或B1,而是引用C1或C2。当您说new ...时,它会创建一个新对象并将其放置在o中(例如,在槽A3中)。它不会影响A1或B1。

如果需要更多解释,请告诉我。


当然,那很有道理。因此,在第一个例子中,o有点像C++指针,因为它本身就是一个变量,因此它的值是另一个变量的地址。因此,当将任何内容分配给它时,设置的是变量本身,而不是它所指向的内容。 - rhughes
没错 :D 不像 C++,Java 没有太多指针控制之类的东西。变量引用内存中的位置,而 = 会重新引用它们。你不能在引用的地方创建一个新变量。但是,你可以更改这个变量。如果 o 已经初始化并且 o.x 是一个字段,你可以更改 o.x 指向的位置,从而重新分配 o.x - Ryan Amos

10

简短回答:是的,确实会发生类似复制的情况。

长回答:你发布的Java foreach循环是语法糖,即

MyObject objects[] = new MyObject[6];

Iterator<MyObject> it = objects.iterator();
while (it.hasNext()) {
   MyObject o = it.next();
   // The previous three lines were from the foreach loop

   // Your code inside the foreach loop
   o = new MyObject();
}

正如展示的去糖版所示,在foreach循环内将引用设置为某个内容不会改变数组的内容。


5
我在每个示例中添加了一个注释,以澄清发生了什么。
第一个示例:
MyObject objects[] = new MyObject[6]; 
for(MyObject o: objects) { 

    // Construct a new object of type MyObject and assign a reference to it into 
    // the iteration variable o. This has no lasting effect, because the foreach
    // loop will automatically assign the next value into the iteration variable
    // in the the next iteration.
    o = new MyObject(); 
} 

第二个例子:

MyObject objects[] = new MyObject[6]; 
for(int i = 0; i < objects.length; i++) { 

    // Construct a new object of type MyObject and store a reference to it into the
    // i-th slot in array objects[]:
    objects[i] = new MyObject(); 
} 

它根本无法编译,因为“in”不是Java中的关键字,你应该在那里放置一个冒号(:)。 - Chandra Sekhar
我只是在用户的原始代码被编辑之前复制了它,结果错过了更新。话虽如此,这并不改变我的回答要点。 - Igor ostrovsky
谁给Igor点了-1,就应该说一下为什么这么做。如果你不提供改进的解释,负面反馈是没有用的。Igor,我怀疑你被点-1是因为缺乏深度。 - Ryan Amos

3
第一个问题是因为foreach循环迭代集合中的元素而未分配数组对象。当您进入该foreach循环时,您的集合中没有任何元素,它只是一个初始化为大小6的空数组,因此不会向您的数组添加对象。另外需要注意的是,即使数组中有元素,foreach循环也不会覆盖它们。
o = new MyObject();

基本上意味着将一个新的MyObject实例分配给o,但是o本身并不是数组objects的一部分,它只是一个临时容器,用于迭代数组的元素,但在这种情况下,没有任何元素。

它并不是真的空的,只是里面有六个 null。其他方面,很好的答案。+1 - Adam Mihalcin
空是指其中没有MyObject的元素。 - Hunter McMillen
我认为我们用不同的措辞表达了相同的意思。 - Adam Mihalcin

2

只有在明确声明要克隆对象时(并且该对象明确实现了克隆功能),才会“复制”对象。

您似乎混淆了引用和名称。

在第一个示例中,在foreach内部,本地变量o引用存储在objects中的某个对象的内存区域。当您执行o = new MyObject()时,会在另一个内存区域初始化一个新的MyObject,然后将o引用重写为指向此新的内存区域。

在第二个示例中,通过编写objects[i] = new MyObject(),您正在表示应重写objects[i]引用,而不是某个本地o变量。


0

我想首先提到的是,非零长度的数组总是可变的。而在foreach循环中

for(MyObject o in objects) 

它的作用是在每次迭代中按照以下方式工作。

o = objects[0] // first iteration 
o = objects[1] // 2nd iteration

但是在您的情况下,您将另一个对象分配给引用o。而不是数组中的对象。就像以下简单的例子一样。
ObjeMyObject objects[] = new MyObject[6];
 MyObject o = Object[0];
 0 = new MyObject();

但是你的原始对象[0]仍然指向一个空对象。


0
每次使用“new”运算符时,JVM都会创建一个新实例,并将其分配给赋值运算符的左操作数。 无论是for each循环还是for循环都没有关系。 在for each循环中, 对于每个MyObject O : Object O只会被创建一次,它将是MyObject的实例,不会被实例化,并且来自Object数组的值将继续复制到O中。
O = Object[0]
O = Object[1]
O = Object[2]
O = Object[3]
O = Object[4]
O = Object[5]

我们不需要关注计数器的增加,这就是 for each 循环的优美之处。


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