我经常遇到代码中反复使用/滥用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 内嵌