通常,我会遇到代码,其中 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分配给变量并使用它有很大的内存/性能优势,因为Getters是Java方法并使用堆栈帧。编码方式真的有相当大的优势吗? }
答案 0 :(得分:15)
您最近描述 意见。
<强>性能:强>
这可能是一个微观优化,在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重构工具也删除了有关必须在一堆地方更改代码的任何争论。
答案 1 :(得分:5)
没有性能差异。如果有的话,它很小。
更重要的是,在存在多个线程的情况下,实际上的两个代码示例完全不同。
说到中途,有人拨打setName
并更改名称。现在你的代码正在经历一条完全意想不到的道路!这实际上是几个历史(甚至内核级)安全漏洞的根本原因。
请注意,我并不主张盲目地将所有结果复制到局部变量中。我只是指出了一个潜在的陷阱。
答案 2 :(得分:2)
如果你在Java中做某事的理由是因为“它可能会表现得更好”,那么你做错了。 JIT编译器将优化琐事public String getName(){return name;}
。相反,你应该根据“这是正确的OOP方式来做决定”来做出决定。
从OOP的角度来看,只使用吸气剂。然后,子类可以根据需要覆盖该方法以执行其他奇特的操作(例如忽略名称“爱因斯坦”)。
答案 3 :(得分:2)
如果将值放在局部变量中,这肯定会更具可读性,这就是为什么你应该这样做而不是因为它表现更好。
另一个巨大的区别是,如果getName()
有副作用(它不应该有,但你不能盲目地信任其他类),或者如果你在多线程系统中工作。在第一种情况下,差异是显而易见的,在第二种情况下,另一个线程可能会在您执行语句时更改值getName()
返回。
在这两种情况下,您可能只需要调用getName()
一次。
答案 4 :(得分:1)
我原则上同意预先优化是邪恶的,但我仍然更喜欢使用第二种形式。主要原因是它与其他情况其他一致,而不是吸气剂,反复调用它是愚蠢的,例如昂贵的操作:
if(person.expensiveOp() != null && person.expensiveOp().equalsIgnoreCase("Einstein")) {
method1(person.expensiveOp());
}
method2(person.expensiveOp());
method3(person.expensiveOp());
method4(person.expensiveOp());
此外,通过语法高亮IDE,我可以隔离值的所有用法。有人会回复说你也可以突出显示该方法的所有用法。但是在对象的不同实例上可能会对同一方法进行多次调用(即toString()
)
答案 5 :(得分:0)
在询问问题并找到我要找的内容之后,我对这个主题进行了一些研究。问题在于,在构建问题时我没有使用正确的术语。我正在寻找'Inline Expansion'(编译器内联)。这是来自Wiki的引用:
在计算中,内联扩展或内联是手动或编译器 用函数体取代函数调用站点的优化 被叫方。此优化可以改善运行时的时间和空间使用, 以增加计划最终规模的可能成本(即 二进制文件大小)。
我还发现此主题已在本网站上讨论过:
1)Do getters and setters impact performance in C++/D/Java?
以下是上述链接的引用:
在Java中,JIT编译器可能迟早会内联它。如 据我所知,JVM JIT编译器只优化了大量使用的代码, 所以你最初可以看到函数调用开销,直到 getter / setter已被充分调用。