Java在for循环中使用getter还是创建局部变量?

我有一个运行4096次的for循环,它应该尽可能快。 性能在这里非常重要。 目前我在循环中使用getter方法,它只返回在循环进行时不会改变的字段中的值或对象。

例:

for (;;) { doSomething(example.getValue()); } 

使用getter是否有任何开销? 使用以下方式更快吗?

例:

 Object object = example.getValue(); for (;;) { doSomething(object); } 

如果是,那么访问example.value等公共字段是否也是如此?

编辑:我不在循环中使用System.out.println()

编辑:某些字段不是final字段。 没有字段是volatile ,也没有synchronized方法(getter)。

正如Rogério所回答的那样 ,在循环外部获取对象引用( Object object = example.getValue(); )可能比在循环内调用getter更快(或者至少永远不会更慢)因为

  • 在“最糟糕”的情况下,尽管getter方法应该是“微不足道的”,但example.getValue()实际上可能会在后台执行一些非常耗费计算的东西。 通过分配一次引用并重新使用它,您只需执行一次这种昂贵的计算。
  • 在“最佳”情况下, example.getValue()做了一些微不足道的事情,比如return value; 因此,在JIT编译器内联代码之后,在循环内部分配它并不比循环外部更昂贵。

但是,更重要的是两者之间的语义差异及其在multithreading环境中可能产生的影响:如果对象example的状态以导致example.getValue()返回对不同对象的引用的方式发生更改,则它是可能的是,在每次迭代中,方法doSomething(Object object)将通过直接调用doSomething(example.getValue());实际上在Object的不同实例上操作doSomething(example.getValue()); 。 另一方面,通过在循环外调用getter并设置对返回实例的引用( Object object = example.getValue(); ), doSomething(object);n次迭代将object运行n次。

这种语义差异可能导致multithreading环境中的行为与单线程环境中的行为完全不同。 此外,这不一定是实际的“内存中”multithreading问题:如果example.getValue()依赖于例如数据库/ HDD /网络资源,则可能在执行循环期间此数据发生更改,从而使其成为可能即使Java应用程序本身是单线程的,也会返回不同的对象。 因此,最好考虑使用循环实际完成的操作,然后选择最能反映预期行为的选项。

这取决于吸气剂。

如果它是一个简单的吸气剂,JIT会将其内联到直接现场接入,因此不存在可测量的差异。 从样式的角度来看,使用getter – 它的代码更少。

如果getter访问volatile字段,则会有额外的内存访问命中,因为该值无法保存在寄存器中,但是命中非常小。

如果getter是synchronized ,那么使用局部变量会明显加快,因为不需要每次调用都可以获取和释放锁,但是循环代码将使用调用getter时字段的潜在过时值。

您应该更喜欢循环外的局部变量,原因如下:

  1. 通过在单行代码中避免嵌套方法调用(如doSomething(example.getValue()) ,并允许代码为值提供更好,更具体的名称,它往往使代码更易于阅读/理解由getter方法返回。
  2. 并非所有的getter方法都是微不足道的(即,它们有时会做一些可能很昂贵的工作),但是开发人员通常不会注意到它,假设给定的方法很简单且价格低廉,而实际上并非如此。 在这种情况下,代码可能会在没有开发人员意识到的情况下受到重大性能影响。 提取到局部变量往往会避免这个问题。

很容易担心性能远远超过必要的。 我明白这感受。 有些事情需要考虑:

  1. 4096并不多,所以除非必须在极短的时间内完成,否则不要太担心性能。
  2. 如果在这个循环中还有其他任何远程昂贵的东西,那么getter无关紧要。
  3. 过早优化是万恶之源。 专注于使您的代码正确和清晰。 然后测量并分析它并缩小最昂贵的东西,并处理它。 如果可能,改进实际算法。

关于你的问题,我不知道JIT究竟做了什么,但除非能够肯定地certificateexample.getValue()example.value在循环中不会改变(除非字段是final否则很难做到)然后,从逻辑上讲,它无法避免在前一个样本中反复调用getter,因为这样可能会改变程序的行为。 重复的电话肯定是一些非零的额外工作。

说了这么多,在循环外创建局部变量,无论它是否更快,因为它更清晰。 也许这会给你带来惊喜,但好的代码并不总是最短的。 表达意图和其他信息非常重要。 在这种情况下,循环外部的局部变量使得读取代码的任何人都明白doSomething的参数不会改变(特别是如果你使它成为最终的),这对于知道很有用。 否则,他们可能需要进行一些额外的挖掘,以确保他们知道程序的行为方式。

如果需要尽快运行它,则不应在关键部分使用System.out.println

关于吸气剂:使用吸气剂有轻微的开销,但你不应该为此烦恼。 Java在JIT编译器中确实有getter和setter优化。 所以最终他们将被本机代码取代。