如果对具体类进行了更改,那么对接口而不是具体类的依赖会减少Java中的编译时间吗?

时间:2018-10-14 14:40:13

标签: java

我在阅读Michael Feathers的“有效地使用旧版代码”和一个子主题“破坏依赖性”时,提到如果一个类依赖于接口,那么如果对具体实现进行了更改,则原始依赖项无需再次编译该类,因为您不直接依赖于实现。

本书参考章节:https://www.safaribooksonline.com/library/view/working-effectively-with/0131177052/ch07.html

我同意这一点,但是通过Java的这种更改,观察到的编译时间是否有显着差异,或者在C ++的情况下这是否更有意义?

我了解了用于提供编译时间优化的C ++中的PIMPL模式或桥接模式,但是在JAVA中也是可能的,还是Java编译器自己进行了这种优化?

3 个答案:

答案 0 :(得分:0)

我从来没有将编译时视为Java的设计考虑因素(自v 1开始使用Java)。具体针对您的问题,语言规范在这方面没有任何保证。可能会因版本而异,实现可能因实现而异。

在javac之外,构建系统可能会做一些明智的事情,例如:https://blog.gradle.org/incremental-compiler-avoidance

编辑:只需仔细检查,javac就会重新编译源代码即使没有更改

$ java -version
openjdk version "10.0.2" 2018-07-17

$ javac *.java && ls -l A.*
-rw-rw-r-- 1 usr usr 143 Oct 14 20:07 A.class
-rw-rw-r-- 1 usr usr  59 Oct 14 19:57 A.java

等待一分钟...

$ javac *.java && ls -l A.*
-rw-rw-r-- 1 usr usr 143 Oct 14 20:08 A.class
-rw-rw-r-- 1 usr usr  59 Oct 14 19:57 A.java

答案 1 :(得分:0)

不,没有显着差异。大部分编译时间都是解析过程,因此完全不受此影响。

重点在于:给定文件“ MySource.java”,其中包含某种类型的MySource,如果重新定义了MySource的任何父类型,则您确实应该重新编译该类。您不必这样做,java会尽力而为,但是现在您可以创建疯狂的场景(例如,如果更新父类型以添加​​新的抽象方法,例如必须执行的方法,现在您可以仍然有一个子类型。尝试混合由不同编译运行产生的这些类时,会抛出运行时错误。

使用接口的目的是避免出现这种情况:即使具体实现发生了变化,也要具有一个完全不变的超类型(没有人碰过接口定义)。

您会认为这意味着:嘿,太好了,现在我什至不必重新编译此文件。但这并没有真正的意义。与C(大量项目仅需几秒钟)相比,java的编译时间非常快,而且要拥有足够智能的构建系统来意识到在这种情况下不需要重新编译是非常困难的。没有;关键是有点自我控制:如果您有一个拆分项目,其中接口/父类位于源存储库A中,而子类位于B中,那么一切都将中断:除非A和B由相同的人维护人并同时重建,您会遇到问题。

因此,重点是:您拥有“公共API”,例如,除非确实有充分的理由,否则您不得更改您决定的类型。使接口更清楚地知道哪些位将不会更改。这使源代码库A的作者可以自由更改(非公共/导出的)实现类,只要他们不弄乱接口,而不必去找源代码库B的好人并告诉他们:嗯,对不起,我们已经改变了事情,您必须进行完全的重建。

答案 2 :(得分:0)

如果更改具体类,则仅需要重新编译具体类及其调用者。如果您有一个接口,而人们在调用该接口而不是具体的类,那么他们就不算作具体类的调用者,也不需要重新编译。这是接口帮助减少编译时间的唯一方法,即AFAICT。

每个具有接口类型优化的Java实现都将优化推迟到运行时(HotSpot)优化器:有关更多信息,您正在寻找的术语是“专业化”。实际上,Java编译器本身(即Java =>字节码编译器)几乎没有进行优化,并且由于无法在运行时推理代码而被限制(参见:Haskell的编译器)。这是因为可以动态加载代码:可以在运行时通过类路径修改和反射来更改接口的可能实现集。