访问者方法性能和优化



我经常遇到代码中反复使用/滥用Getter方法来获取一些值或将其作为方法参数传递,例如:

public class Test {
   public void someMethod() {
      if(person.getName() != null && person.getName().equalsIgnoreCase("Einstein")) {
           method1(person.getName());
      }
      method2(person.getName());
      method3(person.getName());
      method4(person.getName());
   }
}

我通常对它进行编码,如下所示:

public class Test {
   public void someMethod() {
      String name = person.getName();
      if(name != null && name.equalsIgnoreCase("Einstein")) {
           method1(name);
      }
      method2(name);
      method3(name);
      method4(name);
   }

在我看来,将getter分配给变量并使用它具有相当大的内存/性能优势,因为getter是Java方法,使用堆栈帧。这样编码真的有相当大的优势吗?}

您最近是否分析了您的意见

性能:

这可能是1999-2001年在1.2之前的JVM中需要关注的一个微观优化,即使在那时,我也会对它提出质疑,除非有一些严肃的数字表明情况并非如此。

现代JIT实现会告诉您,今天您的观点已经不合时宜了。

现代编译器实现会进行各种优化,这使得在Java中思考这样的事情是浪费时间。JIT只会让它更加浪费关注。

逻辑:

在并发的情况下,您的两个代码块在逻辑上是不等价的,如果您想看到更改,那么制作本地副本将阻止这种情况的发生。根据您想要做的事情,其中一种或另一种方法可能会产生非常微妙的非确定性错误,这些错误很难在更复杂的代码中找到。

特别是如果返回的是可变的东西,而不是不可变的String。然后,即使是本地副本也可能发生更改,除非你进行了深度克隆,否则很容易很快出错。

注意正确地执行,然后衡量并优化重要的内容只要这不会降低代码的可维护性。

JVM将内联对final实例成员的任何调用,并在方法调用中除了return this.name;之外没有其他内容的情况下删除方法调用。它知道访问器方法中没有逻辑,并且它知道引用是final,因此它知道可以内联值,因为它不会更改。

为此,

person.getName() != null && person.getName().equalsIgnoreCase("Einstein") 

更正确地表示为

person != null && "Einstein".equalsIgnoreCase(person.getName())

因为没有机会拥有NullPointerException

重构:

现代IDE重构工具也消除了关于必须在许多地方更改代码的任何争论。

没有性能差异。如果有的话,那就太小了。

更重要的是,在存在多个线程的情况下,这两个代码示例实际上的行为完全不同

比如说,进行到一半时,有人打电话给setName并更改了名称。现在,您的代码正经历一条完全出乎意料的路径!这实际上是造成几个历史(甚至内核级别)安全漏洞的根本原因。

请注意,我并不主张盲目地将得到的所有结果复制到局部变量中。我只是指出了一个潜在的陷阱

如果将值放在局部变量中,它的可读性肯定会高得多,这就是为什么应该这样做,而不是因为它性能更好。

另一个巨大的区别是getName()是否有副作用(它不应该有副作用,但你不能盲目信任其他类),或者你是否在多线程系统中工作。在第一种情况下,差异是明显的,在第二种情况下另一个线程可能会在您执行语句时更改getName()返回的值。

在这两种情况下,您可能希望只调用getName()一次。

如果你在Java中做某事的理由是"它可能会表现得更好",那么你做错了。JIT编译器将优化琐碎的public String getName(){return name;}案例。相反,你应该根据"这是正确的OOP方法吗"来做决定

从OOP的角度来看,只使用getter。然后,子类可以根据需要覆盖该方法来做其他奇特的事情(比如忽略"爱因斯坦"这个名字)。

原则上我同意预优化是邪恶的,但我仍然更喜欢使用第二种形式。主要原因是它与其他情况一致,而不是getter,在getter中重复调用它是愚蠢的,例如一个昂贵的操作:

if(person.expensiveOp() != null && person.expensiveOp().equalsIgnoreCase("Einstein")) {
       method1(person.expensiveOp());
  }
  method2(person.expensiveOp());
  method3(person.expensiveOp());
  method4(person.expensiveOp());

此外,通过语法高亮显示IDE,我可以隔离一个值的所有用法。有人会回答说,你也可以突出显示该方法的所有用法。但是在对象的不同实例(即toString())上可能有多个对同一方法的调用

在提出问题后,我对这个主题进行了一些研究,找到了我想要的东西。问题是我在提出问题时没有使用正确的术语"内联扩展"(编译器内联)是我一直在寻找的。这是一个引用自维基:

在计算中,内联扩展内嵌是一种手动或编译器将函数调用站点替换为被呼叫者。这种优化可以改善运行时的时间和空间使用,以增加程序的最终大小(即。二进制文件大小)。

我还发现这个话题已经在这个网站上讨论过了:

1) getter和setter会影响C++/D/Java中的性能吗?

这是上面链接中的一句话:

在Java中,JIT编译器可能迟早会内联它。像据我所知,JVM JIT编译器只优化了大量使用的代码,因此,您可以在最初看到函数调用开销,直到getter/setter已被调用足够频繁

2) Java 内嵌

最新更新