在循环中创建一个对象

24

在循环中创建对象是一个好的习惯吗?我指的是以下代码:

for(some condition){
    SomeClass a = new SomeClass ();
    System.out.println(a);
}

所以这将为每个迭代创建一个新的SomeClass实例。因此实例数量将等于迭代次数。然后这些实例将稍后由GC收集。

在循环内部重用SomeClass对象是否更好?像这样:

SomeClass a = null;

for(some condition) {
    a = new SomeClass();
    System.out.println(a);
}
据我理解,第二种方式更好,因为它只会在最初创建 SomeClass 对象一次,然后在每次迭代中重复使用它。但是我还有疑虑,请确认这点,或者让我知道我的基础是否不正确。


根据要求返回的结果:

据我理解,第二种方式更好,因为它只会在最初创建 SomeClass 对象一次,然后在每次迭代中重复使用它。但是我还有疑虑,请确认这点,或者让我知道我的基础是否不正确。


3
这里字符串字面量不是最好的例子,因为存在字符串池。而且你的两个示例在每次循环迭代中都创建了一个“新实例”。 - Pshemo
1
两个示例都没有创建对象。声明引用并不等同于创建对象,而字符串字面量是预先创建的。 - Hot Licks
这非常有帮助。一个基本的事实是String是不可变的,在String的情况下,无论哪种情况都不会创建对象。而且,在一般的对象情况下,两者都将创建相等数量的对象。我想我理解得很好了。 - Sambhav Sharma
有人能否确认或否认两个循环都会被JIT优化为相同的行为? - Pshemo
1
@Pshemo - 是的,除非用户在循环外部使用a,否则选择哪个都没有区别。即使生成的代码有非常微小的差异,它对性能的影响也可以忽略不计(并且对GC没有影响)。 - Hot Licks
显示剩余15条评论
9个回答

17

区别在于,第二种情况下,当循环结束时,您的变量a仍将处于作用域内。

除此之外,从垃圾收集的角度来看,它们本质上是相同的。

字符串是引用类型(尽管是不可变的),无论您是否为它们声明一个新变量或仅覆盖每次相同的变量,都没有关系。每次都会创建全新的字符串。


3
请澄清“每次创建全新的字符串”这个概念。我没有记得原帖中的代码创建了任何字符串。 - Hot Licks
3
实际上,不是楼主编辑了它,是其他人编辑了。如果您检查编辑历史,您会发现从未有过执行“new String”操作的版本。 - Hot Licks
@SamIam 在每次迭代中声明变量是否有任何开销?还是仍然相同。 - Optimus Prime
@OptimusPrime 如果有开销,那么对于你来说差异不足以被察觉。 - Sam I am says Reinstate Monica

6
请注意不要混淆“对象”本身和对“对象”的“引用”:
例如,以下代码创建了一个(空)引用,但没有创建任何对象。
Object a = null;

以下代码同时创建了一个对象和对该对象的引用(引用存储在名为'a'的变量中):
Object a = new Object();

下面的代码创建了一个新对象,并将现有(引用)变量“repoints”指向新对象:如果变量'a'已经持有另一个引用,则'a'会忘记它。[但这并不意味着其他变量仍然可能指向由'a'引用的旧对象]。
a = new Object(); // it is the reference 'a' that is 're-used' here not the object...

每次在循环中重新运行上述语句时,您确实会创建一个新对象,并将“a”重新指向该新对象。每次前一个引用(即“a”中保存的引用)都将被遗忘;并且(假设我们这里有一个单线程程序),这意味着它所指向的对象现在将有零个引用指向它:这意味着该对象符合垃圾回收的条件。无论此时是否进行垃圾回收 - 我不知道,恐怕我不能确定。
但我想说的是:在垃圾回收发生的时间方面,您的编码示例没有区别;无论“指针类型”是否已在循环外部定义为“对象”,或者在循环内部重复定义。
以下(无用的)示例可能有助于说明代码一次执行中所执行的“创建对象”和“指向引用”操作之间的区别:
// This creates an object ; but we don't hold a reference to it.
    public class TestClass {
    public static void main(String[] args) {
    for (int n=0;n<100;n++) {
        new Object();
    }
    }
    }

而相比之下:
// This creates a reference ; but no object is created
// This is identical to setting the reference to 'null'.
public class TestClass {
public static void main(String[] args) {
for (int n=0;n<100;n++) {
        Object o;
}
}
}

更正:没有任何语句创建新对象,因为引用字符串字面量不会创建新对象。 - Hot Licks

5

由于String是不可变的,因此两者都创建了相同数量的字符串。每当给String赋新值时,都会创建一个新的String

假设您的示例中意思是使用可变对象。

选项1

for(some condition)
{
    Object o = new Object();
    System.out.println(o);
}

这将在循环的每次迭代中创建一个新的对象o。

选项2

Object o;
for(some condition)
{
    o = new Object();
    System.out.println(o);
}

每次循环都会创建一个新的Object o。

即使对于可变对象,两种方式得到的结果都是相同的!


两者创建的字符串数量相同,因为它们都没有创建任何字符串。 - Hot Licks
1
@HotLicks 你到底在胡言乱语什么? - Rainbolt
在原始代码中(已经被编辑),没有任何字符串被创建。 "foo" 是一个预先存在的字符串字面量。 - Hot Licks
@HotLicks 我的原始答案中没有任何代码,所以我认为您的评论可能错位了。 - Rainbolt
我在谈论原始问题。它已经被编辑成与第一个版本完全不同的样子。 - Hot Licks

3

你混淆了你分配对象的变量和实际对象实例。

这两个代码示例创建了相同数量的对象。第二个示例将在更大的范围内保留一个实例,因此它将在更长时间内可用。


3

第二种方法并不是“更好的”。

String a="foo"; 从字符串池中重用文字字符串。也就是说,无论你在loop内外声明a,在内存方面没有区别。但它们有不同的作用域。我认为这是另一个问题。

即使按照您修改后的版本,使用通用的SomeClass,它也不像您想象的那样:

第二种方式更好,因为这只会创建一次SomeClass对象,并在每次迭代中重复使用它。

它在每个循环步骤中创建新的对象。a只是指向对象的引用。关键是,如果其他对象引用您创建的对象,则GC将不会收集它并释放内存。例如,旧版本(<=java1.6)的String.subString()方法将原始字符串保存为char[],因此GC不会清理原始字符串。


关于字符串的特定行为以及它们是否被池化,你提出了一个很好的观点。 - monojohnny

3
唯一的区别在于,在第二种情况下,当循环结束时变量仍然在作用域内,两种情况下创建的对象数量相等,因为字符串是不可变的。正如你刚才编辑了问题一样,在这种情况下,在每次迭代中都会在内存中创建新的对象。

3

根据我的了解 - 在更大的应用程序中(不在这个小应用程序中),最好使用静态块进行对象创建,因为当类加载到内存中时,静态块代码只会执行一次。从技术上讲,您可以在一个类中有多个静态块,尽管这没有太多意义。

请记住:静态块只能访问静态变量和方法


不仔细考虑就使用静态内容是相当不明智的。 - Hot Licks
@Hot Licks- 谢谢您的意见。 - DRastislav
40年编程经验支持的观点。 - Hot Licks

2

话题有些变化,我更新一下:

如果你真的想要重用曾经创建过的对象,你将需要自己编写代码。它可以遵循以下原则:

SomeClass a = new SomeClass();

for(some condition) {
    a.reset();
    //do something with a
}

SomeClass.reset()方法处理所有细节(这与您实际使用对象的方式有关)。


a = "foo" 将预先创建的 JVM 全局字符串实例 "foo" 分配给引用。因此,不会创建任何东西。 - Hot Licks
那么有什么区别呢? - SebastianH
"foo" 和 new String("foo") 的区别是什么?后者确实会创建一个新的字符串对象。 - Hot Licks
1
@SebastianH 字符串a = "foo",这里的"foo"是一个字符串字面量并存储在Java字符串池中。如果你尝试将相同的字面量分配给另一个引用,它将从字符串池中获取相同的引用。但是new string(..)返回一个字符串类型的对象。 - java seeker
@SambhavSharma 字符串对象是不可变的,而引用是可变的。 - java seeker
显示剩余6条评论

2

这里讲的是作用域问题,

如果你采用第二种方式:

SomeType someFunction(){
   ...
    SomeClass a = null;

    for(some condition) {
        a = new SomeClass();


           System.out.println(a);
        }
     ...
     return something
    }

对于第一种方法,对象a的生命周期仅在循环的单次迭代中存在;而对于someFunction函数,则会在函数结束时从内存中释放。


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