在循环内部或外部声明一个对象?

下面的代码片段是否有任何性能损失?

for (int i=0; i<someValue; i++) { Object o = someList.get(i); o.doSomething; } 

还是这个代码实际上更有意义?

 Object o; for (int i=0; i<someValue; i++) { o = someList.get(i); o.doSomething; } 

如果在字节代码中这两个是完全相同的,那么显然第一个方法在样式上看起来更好,但我想确保是这样。

在今天的编译器中,没有。 我可以在最小的范围内声明对象,因为它对于下一个人来说更具可读性。

引用可能引用Hoare的Knuth的话 :

不成熟的优化是万恶之源。

编译器是否会通过在循环外定义variables来生成稍微快一点的代码是有争议的,我想它不会。 我这会产生相同的字节码。

通过使用循环声明正确确定variables的范围,将其与可能阻止的错误数量进行比较…

在循环中声明Object o没有任何性能损失。 编译器生成非常相似的字节码并进行正确的优化。

请参阅文章Myth – 在循环中定义循环variables对于类似示例的性能不利 。

您可以使用javap -c反汇编代码,并检查编译器实际发出的内容。 在我的设置(用eclipse编译的java 1.5 / mac)中,循环的字节码是相同的。

第一个代码更好,因为它将ovariables的范围限制for块。 从性能angular度来看,它可能对Java没有任何影响,但是可能在较低级别的编译器中有效。 他们可能会把variables放在一个寄存器,如果你做的第一个。

事实上,有些人可能会认为,如果编译器是愚蠢的,则第二个片段在性能方面更好。 这是一些老师在学校告诉我的,我嘲笑他的这个build议! 基本上,编译器只是在方法开始时为方法的局部variables(通过调整堆栈指针)在堆栈上分配内存,并在方法结束时释放它(再次通过调整堆栈指针,假设它不是C ++或者它没有任何析构函数被调用)。 因此,一个方法中的所有基于堆栈的局部variables都是一次分配的,无论它们在哪里声明,需要多less内存。 实际上,如果编译器是愚蠢的,在性能方面没有什么区别,但是如果它足够聪明,第一个代码实际上可以更好,因为它将帮助编译器理解variables的范围和生命周期! 顺便说一句,如果它真的很聪明,那么在性能上就不应该有任何的差别,因为它会推断出实际的范围。

当然,使用new构造一个对象是完全不同的。

我认为可读性更重要的是性能,从可读性的angular度来看,第一代码肯定更好。

我得承认我不懂java。 但这两个等价物呢? 物体的寿命是否一样? 在第一个例子中,我假设(不知道java)o将立即有资格进行垃圾回收循环终止。

但是在第二个例子中,当外部作用域(未示出)退出之前,确定o将不会有资格进行垃圾回收?

不要过早地优化。 比这两者都好:

 for(Object o : someList) { o.doSomething(); } 

因为它消除了样板并澄清了意图。

除非您正在使用embedded式系统,否则所有投注都将closures。 否则,不要试图超越JVM。

我一直认为现在大多数编译器都足够聪明,可以做后者的select。 假设是这样的话,我会说第一个看起来更好。 如果循环变得非常大,则不需要在声明的位置查看全部内容。

这些有不同的语义。 哪个更有意义?

为了“性能原因”重用对象往往是错误的。

问题是对象“意味着什么”? 你为什么创造它? 它代表什么? 对象必须平行于现实世界的事物。 事情被创造,经历状态的变化,并报告其原因。

这些原因是什么? 你的对象是如何build模并反映这些原因的?

了解这个问题的核心… [请注意,如果JLS允许,非JVM实现可能会做不同的事情…]

首先,请记住,例子中的局部variables“o”是一个指针,而不是一个实际的对象。

所有本地variables都以4字节的时隙分配在运行时栈中。 双打和长时间需要两个插槽; 其他的基元和指针都需要一个。 (即使是布尔人也要占满全部空位)

必须为每个方法调用创build固定的运行时堆栈大小。 这个大小由方法中任何给定点需要的最大局部variables“槽”决定。

在上面的例子中,代码的两个版本都需要方法的最大数量的本地variables。

在这两种情况下,都会生成相同的字节码,更新运行时堆栈中的同一个插槽。

换句话说,没有任何性能损失。

不过,根据方法中的其余代码,“循环外声明”版本实际上可能需要更大的运行时堆栈分配。 例如,比较

 for (...) { Object o = ... } for (...) { Object o = ... } 

 Object o; for (...) { /* loop 1 */ } for (...) { Object x =...; } 

在第一个例子中,两个循环都需要相同的运行时堆栈分配。

在第二个例子中,因为“o”超出循环,“x”需要一个额外的运行时间栈槽。

希望这有助于 – 斯科特

在这两种情况下,对象o的types信息都是在编译时确定的。在第二种情况下,o被认为是for循环的全局,在第一种情况下,聪明的Java编译器知道o必须可用于只要循环持续,因此将以这样的方式优化代码,使得在每次迭代中不会有任何types的重新指定。 因此,在这两种情况下,otypes的规格将被执行一次,这意味着唯一的性能差异将在o的范围内。 显然,缩小的范围总是会提高性能,因此要回答你的问题:不,第一个代码片断没有性能损失; 实际上,这个代码片断比第二个更优化。

在第二个剪辑中,o被赋予了不必要的范围,除了作为性能问题之外,它也可能是一个安全问题。

第一个更有意义。 它将variables保留在其使用的范围内,并防止在以后的迭代中使用一次迭代中分配的值,这更具防御性。

前者有时被认为效率更高,但任何合理的编译器都应该能够将其优化为与后者完全相同。

作为维护更多代码而不是编写代码的人。

版本1是首选 – 尽可能保持范围对于理解是必不可less的。 它也更容易重构这种types的代码。

如上所述 – 我怀疑这会在效率上产生什么影响。 事实上,我会争辩说,如果范围更局部,编译器可能会做更多的事情!

当使用multithreading(如果你做50 +),那么我发现这是一个非常有效的方式处理幽灵线程问题:

 Object one; Object two; Object three; Object four; Object five; try{ for (int i=0; i<someValue; i++) { o = someList.get(i); o.doSomething; } }catch(e){ e.printstacktrace } finally{ one = null; two = null; three = null; four = null; five = null; System.gc(); } 

答案部分取决于构造函数的作用以及循环之后对象的情况,因为这在很大程度上决定了代码是如何优化的。

如果对象很大或很复杂,绝对在循环之外声明它。 否则,人们告诉你不要过早地优化是正确的。

实际上,在我面​​前的代码看起来像这样:

 for (int i = offset; i < offset + length; i++) { char append = (char) (data[i] & 0xFF); buffer.append(append); } ... for (int i = offset; i < offset + length; i++) { char append = (char) (data[i] & 0xFF); buffer.append(append); } ... for (int i = offset; i < offset + length; i++) { char append = (char) (data[i] & 0xFF); buffer.append(append); } 

所以,依靠编译器能力,我可以假设只有一个堆栈分配给 ,一个给附加 。 那么一切都会很好,除了重复的代码。

作为一个方面说明,Java应用程序已知是缓慢的。 我从来没有试图在java中进行性能分析,但我认为性能主要来自内存分配pipe理。