标准的Sun javac能进行增量编译吗?

29

最近我开始使用Eclipse的Java编译器,因为它比标准的javac快得多。有人告诉我它更快是因为它执行增量编译。但我还是有点不确定,因为我找不到关于Eclipse和Sun编译器“增量特性”的权威文档。是否真的如此,Sun编译器总是编译每个源文件,而Eclipse编译器仅编译已更改的文件以及受此类更改影响的文件?

编辑:我没有使用Eclipse的自动构建功能,而是设置了

-Dbuild.compiler=org.eclipse.jdt.core.JDTCompilerAdapter

为我的Ant构建。


1
Oracle已经努力通过sjavac工具来解决这个问题。相关问题:https://dev59.com/rV8d5IYBdhLWcg3wzEz4 - aioobe
有人知道是否可以独立于Eclipse安装Eclipse的增量编译器吗?我目前使用Intellij。 - Alexander Mills
4个回答

16

太阳公司的编译器是否始终会编译每个源文件,而Eclipse的编译器只编译已更改的文件以及受这种更改影响的文件?我认为你在两个方面都是正确的。

当然,你也可以强制Eclipse重新编译所有内容。

但是等式的另一部分是,像Ant和Maven这样的Java构建工具能够仅编译已更改的类及其依赖类树。

编辑

在Ant中,增量编译有两种方式:

  • 默认情况下,<javac>任务会比较.java和相应的.class文件的时间戳,并且只会告诉Java编译器重新编译源代码(.java)文件,这些文件要么比其对应的目标文件(.class)文件新,要么根本没有目标文件。

  • <depend>任务还考虑类之间的依赖关系,它通过读取和分析嵌入在.class文件中的依赖信息来确定依赖关系。确定哪些.class文件已经过时后,<depend>任务会删除它们,以便随后的<javac>任务会重新编译它们。然而,这并不完全可靠。例如,源代码的大量更改可能导致<depend>任务分析过时的依赖关系。此外,某些类型的依赖关系(例如静态常量)在.class文件格式中并不明显。

    要了解为什么Ant <depend>不是完全可靠的,请阅读文档中的“限制”部分。


你能解释一下Ant的部分编译是如何工作的吗?Ant如何强制Sun的编译器仅编译更改的文件并忽略源树中的其他文件? - calavera.info
如果依赖类没有改变,为什么需要编译它? - Martin Algesten
因为它所依赖的类型或方法签名已经发生了变化。或者因为它所依赖的编译时常量已经发生了变化。 - Stephen C
并不是完全正确的说法,javac会重建所有内容。Sun的javac有一个扩展选项-Xprefer:newer(默认情况下打开),如果找到源文件和编译后的类,则使用更新的文件。虽然这不会导致正确的增量编译,但在它起作用的情况下可以加快速度。这里有一些演示它的代码 https://github.com/arienkock/java-incremental-builder/blob/master/builder/test/builder/IncrementalTest.java - Kafkaesque
你能否给出一个具体的答案,解释为什么广泛的源代码更改会导致由于过时类而使增量编译失败?我不认为我完全理解你的意思。 - Jonathan Locke
显示剩余4条评论

4

Javac只编译在命令行上命名的源文件或者是依赖项并且过时。Eclipse可能有更精细的方式来决定这意味着什么。


你能提供一些支持这个答案的文档链接吗?我认为1)依赖项不是由普通的javac编译的,2)Eclipse有自己完全不同的编译器,而不仅仅是更细粒度的决策方式。我也无法证明,但这就是我问的原因 :) ... - calavera.info
请参考以下链接:http://download.oracle.com/javase/6/docs/technotes/tools/windows/javac.html#searching。当然,您可以自行验证:只需设置我描述的条件,您就会亲眼看到。 - user207421
这是正确的:javac编译您要编译的文件,而不是“每个源文件”。它不会自行决定需要编译哪些文件,因此在OP所问的意义上它不是增量的。此外(正如EJP所说),当javac正在编译类A并找到其他A依赖的.java文件时,它(根据许多因素)也会将它们编译。这与寻找可能受A.java更改影响的文件不同,我认为这就是人们感到困惑的地方。 - gatkin
@gatkin,您自相矛盾了。为了澄清或重申,javac编译您告诉它的每个文件它可以检测到的任何过时依赖项,这是它决定的 - user207421
@EJP,我发表了评论来解释为什么我要给你点赞。我们都在说javac做了什么。它不会像Eclipse那样查找过时的依赖项,这正是OP所问的。尽管我认为你的答案是正确的,并且已经这样说了,但我认为最后一点值得一提。 - gatkin
@gatkin 我不明白。我不知道“查找A依赖的其他.java文件”和“查找可能受到A更改影响的文件”或“查找过时的依赖项”之间的区别在哪里。我的意思是,你声称你同意的是确定过时内容的技术可能因javac和Eclipse而异。这并不意味着“它不会查找过时的依赖项”。那正是它所做的,也是我的答案陈述的内容。你声称你同意这一点。 - user207421

2

简单概括以下内容,让像我这样的懒人也能明白:

你可以使用ant中的javac任务实现增量构建,但是你应该使用depend任务清除修改后的.java文件的.class文件,并且在javac任务中不要忘记指定includes语句。(仅在javac任务中指定src路径并将includes未指定会导致javac重新编译所有找到的源文件。)

以下是我的依赖和javac任务。 使用标准的Oracle java编译器,只有我修改的.java文件才会被编译。希望这可以帮助你!

<depend srcdir="JavaSource" destdir="${target.classes}" cache="${dependencies.dir}" closure="yes">
    <classpath refid="compiler.classpath" />
    <include name="**/*.java"/>
</depend>

<javac destdir="${target.classes}" debug="true" debuglevel="${debug.features}" optimize="${optimize.flag}" fork="yes" deprecation="no" source="1.6" target="1.6" encoding="UTF-8" includeantruntime="no">
    <classpath refid="compiler.classpath"/>
    <src path="JavaSource"/>
    <include name="**/*.java" />   <!-- This enables the incremental build -->
</javac>

2
当你说“只有我修改的.java文件被编译”时,我看到了一个问题。如果你只是改变了实现细节,那么这样做就没问题,但如果你的更改在外部可见,那么就是不正确的,这在Java中意味着更改非私有方法签名、常量值或参数的默认值... 正确的构建系统一直考虑隐式依赖关系,而Java没有将接口与实现分开存储在不同的文件中,这意味着我们需要像你正在使用的工具一样的工具。Sun/Oracle 25年后仍然缺乏工具支持,这是无法接受的笑话。 - Johan Boulé

2
Eclipse肯定会这样做。如果您打开了此选项(默认情况下),它还会在保存时执行。看起来Sun也不这样做(这很容易测试,只需创建一个小项目,其中A是使用类B的主要类,但B不使用类A。然后更改A并重新编译项目,查看b.class的时间戳是否已更改)。
这是许多编译器的工作方式(例如gcc)。您可以使用ant和make等工具仅编译更改的项目部分。还请注意,这些工具并不完美,有时Eclipse会丢失更改的跟踪,您需要进行完全重建。

Eclipse是如何做到这一点的?Eclipse有自己的javac版本吗?看起来不太可能,但我不确定。 - Alexander Mills
@AlexanderMills Eclipse使用自己的编译器,与Sun/Oracle的javac无关。历史上,IBM早在1997年就有了自己的Java编译器,名为Jikes,仅比Sun发布的时间早一年。 - Johan Boulé

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接