java是多么昂贵的方法调用
我是一个初学者,我一直都认为重复代码是不好的。 但是,为了不这样做,你通常需要额外的方法调用。 假设我有以下课程
public class BinarySearchTree<E extends Comparable<E>>{ private BinaryTree<E> root; private final BinaryTree<E> EMPTY = new BinaryTree<E>(); private int count; private Comparator<E> ordering; public BinarySearchTree(Comparator<E> order){ ordering = order; clear(); } public void clear(){ root = EMPTY; count = 0; } }
将我的clear()方法中的两行复制并粘贴到构造函数中,而不是调用实际的方法,对我来说会更合适吗? 如果有的话,它会产生多大的差别? 如果我的构造函数调用了10个方法,每个方法只需要将一个实例variables设置为一个值呢? 什么是最好的编程习惯?
将我的clear()方法中的两行复制并粘贴到构造函数中,而不是调用实际的方法,对我来说会更合适吗?
编译器可以执行该优化。 JVM也是如此。 编译器编写者和JVM作者使用的术语是“内联扩展”。
如果有的话,它会产生多大的差别?
测量它。 通常情况下,你会发现它没有任何区别。 如果你认为这是一个表演热点,你看错了地方; 这就是为什么你需要测量它。
如果我的构造函数调用了10个方法,每个方法只需要将一个实例variables设置为一个值呢?
同样,这取决于生成的字节码和Java虚拟机执行的任何运行时优化。 如果编译器/ JVM可以内联方法调用,它将执行优化以避免在运行时创build新的堆栈帧的开销。
什么是最好的编程习惯?
避免过早优化。 最好的做法是编写可读且devise良好的代码,然后针对应用程序中的性能热点进行优化。
其他人所说的优化是绝对正确的。
从performance的angular度来看 ,没有任何理由要把这种方法联系起来。 如果这是一个性能问题,那么JVM中的JIT将内联它。 在java中,方法调用非常接近于自由,所以不值得考虑它。
这就是说,这里有一个不同的问题。 也就是说,从构造函数中调用一个可重写的方法(即,不是final
, static
或private
) 是不好的编程习惯。 (Effective Java,第2版,第89页,标题为“用于inheritance或者禁止的devise和文档”)
如果有人添加一个名为LoggingBinarySearchTree
的BinarySearchTree
子类,会覆盖所有公共方法,如下所示:
public void clear(){ this.callLog.addCall("clear"); super.clear(); }
那么LoggingBinarySearchTree
将永远不会被构造! 问题是当BinarySearchTree
构造函数正在运行时, this.callLog
将为null
,但被调用的clear
是被覆盖的,你将得到一个NullPointerException
。
请注意,Java和C ++在这里有所不同:在C ++中,调用virtual
方法的超类构造函数最终调用超类中定义的类,而不是覆盖类。 在两种语言之间切换的人有时会忘记这一点。
鉴于此,我认为在从构造函数中调用 clear
方法时,可能会更clear
,但是在Java中,您应该继续前进并调用所需的所有方法。
我肯定会离开它。 如果你改变clear()
逻辑呢? find复制了2行代码的所有地方是不切实际的。
最好的做法是两次测量并切割一次。
一旦你浪费了时间的优化,你永远无法再回来! (所以先量一下,问问自己是否值得优化,你能节省多less实际时间?)
在这种情况下,Java VM可能已经在做你正在谈论的优化。
如果需要将值传递给方法,则方法调用的成本是创build(和处理)堆栈帧和一些额外的字节码expression式。
一般来说(作为一个初学者,这意味着永远!)你永远不应该像你正在考虑的微观优化。 始终偏爱这样的代码的可读性。
为什么? 因为编译器/热点将会为你提供这些优化,还有更多。 如果有的话,当你试着沿着这些线进行优化时(尽pipe不是这种情况),你可能会让事情变慢。 Hotspot了解常见的编程习惯用法,如果您尝试自己进行优化,可能不会理解您要做什么,因此无法对其进行优化。
还有更大的维护成本。 如果你开始重复代码,那么将会更加努力维护,这可能比你想象的要麻烦得多!
顺便说一下,在编码生活中,你可能会得到一些你需要做出低级优化的点 – 但是如果你达到这些点,你一定会知道什么时候到了。 如果你不这样做的话,如果你需要的话,你可以随时回头优化。
我遵循的模式是这个问题的方法是否满足以下条件之一:
- 这个方法以外的方法有用吗?
- 在其他方法中使用这种方法会有帮助吗?
- 每次我需要重写这个会不会令人沮丧?
- 该方法的多function性可以通过使用几个参数来增加?
如果上述任何一个都是真的,就应该用自己的方法来包装。
保持clear()
方法有助于可读性。 有不可维护的代码是更昂贵的。
优化编译器通常在从这些“额外”操作中去除冗余方面做得相当不错。 在许多情况下,“优化”代码和代码之间的区别只是按照你想要的方式编写,而通过优化编译器运行则是没有的; 也就是说,优化编译器通常和你做的一样好,而且不影响源代码的性能。 事实上,很多时候,“手动优化”的代码效率低,因为编译器在进行优化时会考虑很多事情。 保持代码的可读格式,不要担心优化,直到以后。
“不成熟的优化是万恶之源”。 Donald Knuth
我不会担心方法调用,而是方法的逻辑。 如果这是关键系统,而系统需要“快速”,那么我会考虑优化代码,需要很长时间才能执行。
鉴于现代计算机的记忆,这是非常便宜的。 将代码分解成方法总是更好,这样可以快速读取正在发生的事情。 如果错误被限制在一个只有几行的方法中,它也将有助于缩小代码中的错误。
正如其他人所说,方法调用的代价是微不足道的,因为编译器会为你优化它。
也就是说,从构造函数中调用实例方法的方法存在危险。 运行后面更新实例方法的风险,以便它可能尝试使用尚未由构造函数启动的实例variables。 也就是说,你不一定要把施工活动从施工单位中分离出来。
另一个问题 – 您的clear()方法将根设置为EMPTY,在创build对象时初始化它。 如果您将节点添加到EMPTY,然后调用clear(),则不会重置根节点。 这是你想要的行为吗?