循环内的变量声明

8

在编程中,我经常遇到一个共同的困境,即关于在循环中声明变量的问题。比如说我需要执行以下操作:

List list=myObject.getList();
Iterator itr=list.iterator();
while (itr.hasNext()){
    BusinessObject myBo=(BusinessObject)itr.next();
    process(myBo);
}

在上面的代码片段中,myBo应该在循环外声明,将其声明在循环内不会对内存和性能造成伤害。
9个回答

8

在循环内部声明不会对内存和性能造成任何影响。


5

如果可能的话,使用 List<BusinessObject>Iterator<BusinessObject> 来避免强制转换:

List<BusinessObject> list = myObject.getList();
Iterator<BusinessObject> itr = list.iterator();

while (itr.hasNext()) {
   process(itr.next());
}

3
当然,这只适用于1.5及以上版本。并非每个人都那么幸运 :) - extraneon

5
良好的软件设计原则之一是限制局部变量的范围,即在块内声明它们,并在该变量的最后一次使用后不久结束该块。这不会影响性能或其他“硬”方面,但可以使程序更易于阅读和分析。
总之,你正在做的事情被认为是好的。

4
您的循环最优雅的解决方案是增强型for循环(Java 5或更新版本):
List<BusinessObject> list = myObject.getList();

for( BusinessObject myBo : list ) {
    process(myBo);
}

即使您提供的代码,也不会存在性能问题,因为所有临时变量仅保存对BusinessObject的引用,这非常便宜。


4

myBo只是一个对象的引用(由itr.next()返回)。因此,它所需的内存非常小,只创建一次,在循环中添加它不应影响您的程序。在我看来,将其声明在使用它的循环内部实际上有助于使代码更易读。


这个答案是误导性的,因为它暗示了引用变量在每次迭代中只分配一次,这是不正确的。 - BlueRaja - Danny Pflughoeft
正如我在答案中所说的,它仅仅是创建了一个对象的引用,而不是分配整个对象本身。这就是为什么所需内存数量非常小的原因——因为只涉及到引用所需的几个字节。 - @BlueRaja - Danny Pflughoeft - MAK
我明白,但即使是那个引用(指针本身)也只分配一次,这与短语“分配/释放它不应显着影响您的程序”的含义相反。 - BlueRaja - Danny Pflughoeft
@BlueRaja - Danny Pflughoeft:我现在意识到这可能会被误读。修改了我的答案以使其更清晰。 - MAK

2

它不会造成任何内存损害。

顺便说一句,除非你省略了某些代码,否则可以完全跳过声明:

while (itr.hasNext()){
    //BusinessObject myBo=(BusinessObject)itr.next();
    process((BusinessObject)itr.next());
} 

1

使用javap -c [ClassName]查看字节码。这里有一个演示循环中单次使用变量的类的示例。相关的字节码转储在注释中:

class HelloWorldLoopsAnnotated {
    //
    // HelloWorldLoopsAnnotated();
    //   Code:
    //    0:   aload_0
    //    1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
    //    4:   return
    ////////////////////////////////////////////////////////////////////////////

    void stringDeclaredInsideLoop(){
        while (true) {
            // 0:   ldc #2; //String Hello World!
            String greeting = "Hello World!";
            doNothing(greeting);
        }
    }
    //
    // void stringDeclaredInsideLoop();
    //   Code:
    //    0:   ldc #2; //String Hello World!
    //    2:   astore_1
    //    3:   aload_0
    //    4:   aload_1
    //    5:   invokespecial   #3; //Method doNothing:(Ljava/lang/String;)V
    //    8:   goto    0
    ////////////////////////////////////////////////////////////////////////////

    void stringDeclaredOutsideLoop(){
        String greeting;
        while (true) {
            greeting = "Hello World!";
            doNothing(greeting);
        }
    }
    //
    // void stringDeclaredOutsideLoop();
    //   Code:
    //    0:   ldc #2; //String Hello World!
    //    2:   astore_1
    //    3:   aload_0
    //    4:   aload_1
    //    5:   invokespecial   #3; //Method doNothing:(Ljava/lang/String;)V
    //    8:   goto    0
    ////////////////////////////////////////////////////////////////////////////

    void stringAsDirectArgument(){
        while (true) {
            doNothing("Hello World!");
        }
    }
    // void stringAsDirectArgument();
    //   Code:
    //    0:   aload_0
    //    1:   ldc #2; //String Hello World!
    //    3:   invokespecial   #3; //Method doNothing:(Ljava/lang/String;)V
    //    6:   goto    0
    ////////////////////////////////////////////////////////////////////////////

    private void doNothing(String s) {
    }
}

stringDeclaredInsideLoop()stringDeclaredOutsideLoop() 产生相同的六条指令的字节码。然而,stringDeclaredInsideLoop() 仍然胜出:有限的作用域是最好的

经过一些思考,我真的看不出缩小范围如何影响性能:堆栈中相同的数据将需要相同的指令。

然而,stringAsDirectArgument() 只定义了四个指令的操作。低内存环境(例如我的愚蠢手机)可能会欣赏优化,但同事阅读您的代码时可能不会,因此在从代码中删除字节之前请行使判断力。

有关更多信息,请参见完整的gist


1

如果很简短——不行。 在C++中,如果通过复制来创建myBo,则可能会有问题, 但在Java中,始终使用引用,不是吗?
为了性能,最好优化在process()中执行的操作。


0

临时引用myBo被放置在堆栈上,应该被大部分优化掉。您的代码不应该有任何性能损失。


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