Java8 具有数千个同名默认方法的接口编译缓慢
Java8 slow compiling for interfaces with thousands of default methods with the same name
鉴于接口(非常大并且由语言定义生成):
interface VisitorA {
default void visit(ASTA1 node) {...}
...
default void visit(ASTA2000 node) {...}
}
interface VisitorB extends VisitorA {
default void visit(ASTB1 node) {...}
...
default void visit(ASTB1000 node) {...}
// due to language embedding all visit methods of VisitorA
// must be overwritten
@Override
default void visit(ASTA1 node) {...}
...
@Override
default void visit(ASTA2000 node) {...}
}
interface VisitorC extends VisitorA {
default void visit(ASTC1 node) {...}
...
default void visit(ASTC1000 node) {...}
// due to language embedding all visit methods of VisitorA
// must be overwritten
@Override
default void visit(ASTA1 node) {...}
...
@Override
default void visit(ASTA2000 node) {...}
}
interface VisitorD extends VisitorB, VisitorC {
default void visit(ASTD1 node) {...}
...
default void visit(ASTD1000 node) {...}
// due to language embedding all visit methods of VisitorA,
// VisitorB, and VisitorC must be overwritten
@Override
default void visit(ASTA1 node) {...}
...
@Override
default void visit(ASTA2000 node) {...}
@Override
default void visit(ASTB1 node) {...}
...
@Override
default void visit(ASTB1000 node) {...}
@Override
default void visit(ASTC1 node) {...}
...
@Override
default void visit(ASTC1000 node) {...}
}
现在编译接口VisitorA(包含大约2.000个重载方法)大约需要10s。
编译接口 VisitorB 和 VisitorC 各需要大约 1.5 分钟。
但是当我们尝试编译接口VisitorD时,Java8编译需要7分钟左右!
- 有人知道为什么编译 VisitorD 需要这么多时间吗?
- 是不是继承了默认的方法?
- 还是因为钻石星座的缘故,VisitorB和VisitorC都扩展了VisitorA,VisitorD又扩展了VisitorB和VisitorC?
我们已经尝试过,以下解决方案有所帮助:
interface VisitorAPlain {
void visit(ASTA1 node);
...
void visit(ASTA2000 node);
}
interface VisitorA extends VisitorAPlain {
... // has same default methods as VisitorA above
}
interface VisitorBPlain extends VisitorAPlain {
void visit(ASTB1 node);
...
void visit(ASTB1000 node);
}
interface VisitorB extends VisitorBPlain {
... // has same default methods as VisitorB above
}
interface VisitorCPlain extends VisitorAPlain {
void visit(ASTC1 node);
...
void visit(ASTC1000 node);
}
interface VisitorC extends VisitorCPlain {
... // has same default methods as VisitorC above
}
interface VisitorD extends VisitorBPlain, VisitorCPlain {
default void visit(ASTD1 node) {...}
...
default void visit(ASTD1000 node) {...}
// due to language embedding all visit methods of VisitorAPlain,
// VisitorBPlain, and VisitorCPlain must be overwritten
@Override
default void visit(ASTA1 node) {...}
...
default void visit(ASTA2000 node) {...}
@Override
default void visit(ASTB1 node) {...}
...
default void visit(ASTB1000 node) {...}
@Override
default void visit(ASTC1 node) {...}
...
default void visit(ASTC1000 node) {...}
}
而现在visitorD的编译时间只需要2分钟左右
但这还是很多。
- 有人知道如何将 VisitorD 的编译时间缩短到几秒钟吗?
- 如果我们去掉VisitorD的两个extends关系,
extends VisitorBPlain, VisitorCPlain
,那么这个接口的编译时间大约需要15s——尽管它有大约5000个默认方法。但是出于转换原因,我们需要 VisitorD 与 VisitorB 和 VisitorC 兼容(通过直接扩展或具有中间纯接口的间接扩展)。
我也看过类似问题的答案:
但问题似乎出在泛型类型推断上:
"There's a severe performance regression in Java 8 when it comes to overload resolution based on generic target typing."
所以这有点不同,如果有人有小费或好的
解释为什么会这样;我将不胜感激。
谢谢,
迈克尔
此答案归功于@Brian Goetz。
我创建了一个虚拟测试,一旦所有 visit
方法都被覆盖和重载,而在另一时间 visitX
方法有不同的名称。
结果比我想象的更惊人:
在重载和覆盖 visit
方法时,编译器需要将近 30 分钟!
当我在一个访问者 class 中唯一重命名 visit
方法时,编译器只需要 46 秒 .
这是虚拟测试的源代码:
https://drive.google.com/open?id=0B6L6K365bELNUkVYMHZnZ0dGREk
下面是我电脑上编译时的截图:
VisitorN
包含重载和覆盖的 visit
方法。
VisitorG
包含优化的 visitX
方法,这些方法只是被覆盖但不再重载。
使用"plain"方法和不同的visitX
方法,然后编译Visitor_S
和VisitorPlain_S
只需要大约22秒 (比直接重载 default visitX
方法的方法快两倍)。
Visitor_S
有 default
方法,但它扩展了 VisitorPlain_S
没有 default
方法。 VisitorPlain_S
扩展了其他 "plain" 没有 default
方法的访问者。
但我仍然不明白——只是出于我的理论兴趣,是桥接方法的事实:
在 https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html 桥接方法中只出现类型擦除,但在示例中我们没有泛型,因此类型擦除根本不应该起作用。 - 也许任何人都能很好地解释为什么它仍然很重要。
经过针对这个问题的额外会议,我们发现了第一个答案的以下局限性:
第一个答案对 "static" 访问者非常有用,因为它们在 ANTLR 中使用,因为那里没有语言界面,所以 visit
方法确切地知道 children ASTTypes
。在 MontiCore 中我们可以定义一个接口语法元素,现在将在这里解释:
grammar MontiArc {
MontiArc = "component" Name "{" ArcElement* "}";
interface ArcElement;
Port implements ArcElement = "port" ... ;
}
grammar MontiArcAutomaton extends MontiArc {
Automaton implements ArcElement = State | Transition;
State = "state" ... ;
Transition = ... "->" ...;
}
MontiArcAST
的访问者不知道应该调用哪个 accept
方法,因为您不知道是否应该调用 PortAST#accept
甚至不知道的方法 State#accept
,由于语法扩展,后面会介绍。这就是我们使用 "double dispatching" 的原因,但因此 visit
方法必须具有相同的名称(因为我们无法知道方法 visitState(StateAST node)
,当我们为 MontiArc
语法。
我们考虑生成 visitX
方法并使用大型 instanceof
-if-cascade 从一般 visit
方法委托给此方法。但这需要在部署语法 MontiArc
的 jar-File 之后向 visit(MontiArcAST node)
添加额外的 if
语句,这会破坏我们的模块性。
我们将尝试进一步分析问题,如果我们找到一种新的方法来生成大量动态访问者,我会留给您up-to-date。
我们想出了解决问题的方法:
我们在生成器中有一个错误,因为重载的继承方法有
与继承自的方法体相同。
这对我们来说意味着我们有两种解决方法:
- (a) 不再生成我们继承的方法
- (b) 生成所有方法,但删除接口继承
有趣的是 (a) 比 (b).
需要更多的编译时间
我在 Mac 上做了一个实验来表示我们在修复过程中发现的结果,您可以在以下网址下载:
https://drive.google.com/open?id=0B6L6K365bELNWDRoeTF4RXJsaFk
我这里只是描述实验的基本文件,以及结果。也许有人觉得它有用。
版本 1 是 (b),看起来像:
DelegatorVisitorA.java
interface DelegatorVisitorA extends VisitorA {
VisitorA getVisitorA();
default void visit(AST_A1 node) {
getVisitorA().visit(node);
}
...
default void visit(AST_A49 node) {
getVisitorA().visit(node);
}
}
DelegatorVisitorB.java
interface DelegatorVisitorB extends VisitorB {
VisitorA getVisitorA();
default void visit(AST_A1 node) {
getVisitorA().visit(node);
}
...
default void visit(AST_A49 node) {
getVisitorA().visit(node);
}
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
}
DelegatorVisitorC.java
interface DelegatorVisitorC extends VisitorC {
VisitorA getVisitorA();
default void visit(AST_A1 node) {
getVisitorA().visit(node);
}
...
default void visit(AST_A49 node) {
getVisitorA().visit(node);
}
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
VisitorC getVisitorC();
default void visit(AST_C1 node) {
getVisitorC().visit(node);
}
...
default void visit(AST_C49 node) {
getVisitorC().visit(node);
}
}
版本 2 是 (a),看起来像:
DelegatorVisitorA.java 与版本 1 相同
DelegatorVisitorB.java
interface DelegatorVisitorB extends VisitorB , DelegatorVisitorA{
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
}
DelegatorVisitorC.java
interface DelegatorVisitorC extends VisitorC , DelegatorVisitorB{
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
}
版本 3(我们有一个中间步骤,但它也是错误的)看起来像:
DelegatorVisitorA.java 与版本 1 相同
DelegatorVisitorB.java
interface DelegatorVisitorB extends VisitorB , DelegatorVisitorA{
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
}
DelegatorVisitorC.java
interface DelegatorVisitorC extends VisitorC , DelegatorVisitorA, DelegatorVisitorB{
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
}
版本 4(导致此 post 的旧版本)看起来像:
DelegatorVisitorA.java 与版本 1 相同
DelegatorVisitorB.java
interface DelegatorVisitorB extends VisitorB , DelegatorVisitorA{
VisitorA getVisitorA();
default void visit(AST_A1 node) {
getVisitorA().visit(node);
}
...
default void visit(AST_A49 node) {
getVisitorA().visit(node);
}
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
}
DelegatorVisitorC.java
interface DelegatorVisitorC extends VisitorB , DelegatorVisitorA, DelegatorVisitorB{
VisitorA getVisitorA();
default void visit(AST_A1 node) {
getVisitorA().visit(node);
}
...
default void visit(AST_A49 node) {
getVisitorA().visit(node);
}
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
VisitorC getVisitorC();
default void visit(AST_C1 node) {
getVisitorC().visit(node);
}
...
default void visit(AST_C49 node) {
getVisitorC().visit(node);
}
}
这里我只展示了不同版本的DelegatorVisitorA.java、DelegatorVisitorB.java和DelegatorVisitorC.java。
其他委托人访问者 DelegatorVisitorD.java 到 DelegatorVisitorI.java 遵循相同的模式。 (DelegatorVisitorI属于语言I扩展了语言H,语言H有DelegatorVisitorH,语言H扩展了语言G,以此类推。)
编译DelegatorVisitorI.java的结果在上述四个不同的版本中生成需要很长时间:
结果为:
Version 1:
103-240:srcV1 michael$ time javac DelegatorVisitorI.java
real 0m1.859s
user 0m5.023s
sys 0m0.175s
Version 2:
103-240:srcV2 michael$ time javac DelegatorVisitorI.java
real 0m3.364s
user 0m7.713s
sys 0m0.342s
Version 3:
103-240:srcV3 michael$ time javac DelegatorVisitorI.java
real 2m58.009s
user 2m56.787s
sys 0m1.718s
Version 4:
103-240:srcV4 michael$ time javac DelegatorVisitorI.java
real 14m14.923s
user 14m3.738s
sys 0m5.141s
所有四个不同版本的 Java 文件具有相同的行为,
但由于代码重复,编译过程需要更长的时间。
另外有趣的是,如果你复制方法而不使用任何继承,那么编译是最快的,即使文件在很长的继承链之后变得更大。
(版本2和版本3的时间差大我个人无法理解,可能是javac编译器解析过程中的bug)
鉴于接口(非常大并且由语言定义生成):
interface VisitorA {
default void visit(ASTA1 node) {...}
...
default void visit(ASTA2000 node) {...}
}
interface VisitorB extends VisitorA {
default void visit(ASTB1 node) {...}
...
default void visit(ASTB1000 node) {...}
// due to language embedding all visit methods of VisitorA
// must be overwritten
@Override
default void visit(ASTA1 node) {...}
...
@Override
default void visit(ASTA2000 node) {...}
}
interface VisitorC extends VisitorA {
default void visit(ASTC1 node) {...}
...
default void visit(ASTC1000 node) {...}
// due to language embedding all visit methods of VisitorA
// must be overwritten
@Override
default void visit(ASTA1 node) {...}
...
@Override
default void visit(ASTA2000 node) {...}
}
interface VisitorD extends VisitorB, VisitorC {
default void visit(ASTD1 node) {...}
...
default void visit(ASTD1000 node) {...}
// due to language embedding all visit methods of VisitorA,
// VisitorB, and VisitorC must be overwritten
@Override
default void visit(ASTA1 node) {...}
...
@Override
default void visit(ASTA2000 node) {...}
@Override
default void visit(ASTB1 node) {...}
...
@Override
default void visit(ASTB1000 node) {...}
@Override
default void visit(ASTC1 node) {...}
...
@Override
default void visit(ASTC1000 node) {...}
}
现在编译接口VisitorA(包含大约2.000个重载方法)大约需要10s。 编译接口 VisitorB 和 VisitorC 各需要大约 1.5 分钟。 但是当我们尝试编译接口VisitorD时,Java8编译需要7分钟左右!
- 有人知道为什么编译 VisitorD 需要这么多时间吗?
- 是不是继承了默认的方法?
- 还是因为钻石星座的缘故,VisitorB和VisitorC都扩展了VisitorA,VisitorD又扩展了VisitorB和VisitorC?
我们已经尝试过,以下解决方案有所帮助:
interface VisitorAPlain {
void visit(ASTA1 node);
...
void visit(ASTA2000 node);
}
interface VisitorA extends VisitorAPlain {
... // has same default methods as VisitorA above
}
interface VisitorBPlain extends VisitorAPlain {
void visit(ASTB1 node);
...
void visit(ASTB1000 node);
}
interface VisitorB extends VisitorBPlain {
... // has same default methods as VisitorB above
}
interface VisitorCPlain extends VisitorAPlain {
void visit(ASTC1 node);
...
void visit(ASTC1000 node);
}
interface VisitorC extends VisitorCPlain {
... // has same default methods as VisitorC above
}
interface VisitorD extends VisitorBPlain, VisitorCPlain {
default void visit(ASTD1 node) {...}
...
default void visit(ASTD1000 node) {...}
// due to language embedding all visit methods of VisitorAPlain,
// VisitorBPlain, and VisitorCPlain must be overwritten
@Override
default void visit(ASTA1 node) {...}
...
default void visit(ASTA2000 node) {...}
@Override
default void visit(ASTB1 node) {...}
...
default void visit(ASTB1000 node) {...}
@Override
default void visit(ASTC1 node) {...}
...
default void visit(ASTC1000 node) {...}
}
而现在visitorD的编译时间只需要2分钟左右 但这还是很多。
- 有人知道如何将 VisitorD 的编译时间缩短到几秒钟吗?
- 如果我们去掉VisitorD的两个extends关系,
extends VisitorBPlain, VisitorCPlain
,那么这个接口的编译时间大约需要15s——尽管它有大约5000个默认方法。但是出于转换原因,我们需要 VisitorD 与 VisitorB 和 VisitorC 兼容(通过直接扩展或具有中间纯接口的间接扩展)。
我也看过类似问题的答案:
所以这有点不同,如果有人有小费或好的 解释为什么会这样;我将不胜感激。
谢谢, 迈克尔
此答案归功于@Brian Goetz。
我创建了一个虚拟测试,一旦所有 visit
方法都被覆盖和重载,而在另一时间 visitX
方法有不同的名称。
结果比我想象的更惊人:
在重载和覆盖 visit
方法时,编译器需要将近 30 分钟!
当我在一个访问者 class 中唯一重命名 visit
方法时,编译器只需要 46 秒 .
这是虚拟测试的源代码: https://drive.google.com/open?id=0B6L6K365bELNUkVYMHZnZ0dGREk
下面是我电脑上编译时的截图:
VisitorN
包含重载和覆盖的 visit
方法。
VisitorG
包含优化的 visitX
方法,这些方法只是被覆盖但不再重载。
使用"plain"方法和不同的visitX
方法,然后编译Visitor_S
和VisitorPlain_S
只需要大约22秒 (比直接重载 default visitX
方法的方法快两倍)。
Visitor_S
有 default
方法,但它扩展了 VisitorPlain_S
没有 default
方法。 VisitorPlain_S
扩展了其他 "plain" 没有 default
方法的访问者。
但我仍然不明白——只是出于我的理论兴趣,是桥接方法的事实: 在 https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html 桥接方法中只出现类型擦除,但在示例中我们没有泛型,因此类型擦除根本不应该起作用。 - 也许任何人都能很好地解释为什么它仍然很重要。
经过针对这个问题的额外会议,我们发现了第一个答案的以下局限性:
第一个答案对 "static" 访问者非常有用,因为它们在 ANTLR 中使用,因为那里没有语言界面,所以 visit
方法确切地知道 children ASTTypes
。在 MontiCore 中我们可以定义一个接口语法元素,现在将在这里解释:
grammar MontiArc {
MontiArc = "component" Name "{" ArcElement* "}";
interface ArcElement;
Port implements ArcElement = "port" ... ;
}
grammar MontiArcAutomaton extends MontiArc {
Automaton implements ArcElement = State | Transition;
State = "state" ... ;
Transition = ... "->" ...;
}
MontiArcAST
的访问者不知道应该调用哪个 accept
方法,因为您不知道是否应该调用 PortAST#accept
甚至不知道的方法 State#accept
,由于语法扩展,后面会介绍。这就是我们使用 "double dispatching" 的原因,但因此 visit
方法必须具有相同的名称(因为我们无法知道方法 visitState(StateAST node)
,当我们为 MontiArc
语法。
我们考虑生成 visitX
方法并使用大型 instanceof
-if-cascade 从一般 visit
方法委托给此方法。但这需要在部署语法 MontiArc
的 jar-File 之后向 visit(MontiArcAST node)
添加额外的 if
语句,这会破坏我们的模块性。
我们将尝试进一步分析问题,如果我们找到一种新的方法来生成大量动态访问者,我会留给您up-to-date。
我们想出了解决问题的方法: 我们在生成器中有一个错误,因为重载的继承方法有 与继承自的方法体相同。
这对我们来说意味着我们有两种解决方法:
- (a) 不再生成我们继承的方法
- (b) 生成所有方法,但删除接口继承
有趣的是 (a) 比 (b).
需要更多的编译时间我在 Mac 上做了一个实验来表示我们在修复过程中发现的结果,您可以在以下网址下载: https://drive.google.com/open?id=0B6L6K365bELNWDRoeTF4RXJsaFk
我这里只是描述实验的基本文件,以及结果。也许有人觉得它有用。
版本 1 是 (b),看起来像:
DelegatorVisitorA.java
interface DelegatorVisitorA extends VisitorA {
VisitorA getVisitorA();
default void visit(AST_A1 node) {
getVisitorA().visit(node);
}
...
default void visit(AST_A49 node) {
getVisitorA().visit(node);
}
}
DelegatorVisitorB.java
interface DelegatorVisitorB extends VisitorB {
VisitorA getVisitorA();
default void visit(AST_A1 node) {
getVisitorA().visit(node);
}
...
default void visit(AST_A49 node) {
getVisitorA().visit(node);
}
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
}
DelegatorVisitorC.java
interface DelegatorVisitorC extends VisitorC {
VisitorA getVisitorA();
default void visit(AST_A1 node) {
getVisitorA().visit(node);
}
...
default void visit(AST_A49 node) {
getVisitorA().visit(node);
}
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
VisitorC getVisitorC();
default void visit(AST_C1 node) {
getVisitorC().visit(node);
}
...
default void visit(AST_C49 node) {
getVisitorC().visit(node);
}
}
版本 2 是 (a),看起来像:
DelegatorVisitorA.java 与版本 1 相同
DelegatorVisitorB.java
interface DelegatorVisitorB extends VisitorB , DelegatorVisitorA{
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
}
DelegatorVisitorC.java
interface DelegatorVisitorC extends VisitorC , DelegatorVisitorB{
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
}
版本 3(我们有一个中间步骤,但它也是错误的)看起来像:
DelegatorVisitorA.java 与版本 1 相同
DelegatorVisitorB.java
interface DelegatorVisitorB extends VisitorB , DelegatorVisitorA{
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
}
DelegatorVisitorC.java
interface DelegatorVisitorC extends VisitorC , DelegatorVisitorA, DelegatorVisitorB{
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
}
版本 4(导致此 post 的旧版本)看起来像:
DelegatorVisitorA.java 与版本 1 相同
DelegatorVisitorB.java
interface DelegatorVisitorB extends VisitorB , DelegatorVisitorA{
VisitorA getVisitorA();
default void visit(AST_A1 node) {
getVisitorA().visit(node);
}
...
default void visit(AST_A49 node) {
getVisitorA().visit(node);
}
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
}
DelegatorVisitorC.java
interface DelegatorVisitorC extends VisitorB , DelegatorVisitorA, DelegatorVisitorB{
VisitorA getVisitorA();
default void visit(AST_A1 node) {
getVisitorA().visit(node);
}
...
default void visit(AST_A49 node) {
getVisitorA().visit(node);
}
VisitorB getVisitorB();
default void visit(AST_B1 node) {
getVisitorB().visit(node);
}
...
default void visit(AST_B49 node) {
getVisitorB().visit(node);
}
VisitorC getVisitorC();
default void visit(AST_C1 node) {
getVisitorC().visit(node);
}
...
default void visit(AST_C49 node) {
getVisitorC().visit(node);
}
}
这里我只展示了不同版本的DelegatorVisitorA.java、DelegatorVisitorB.java和DelegatorVisitorC.java。 其他委托人访问者 DelegatorVisitorD.java 到 DelegatorVisitorI.java 遵循相同的模式。 (DelegatorVisitorI属于语言I扩展了语言H,语言H有DelegatorVisitorH,语言H扩展了语言G,以此类推。)
编译DelegatorVisitorI.java的结果在上述四个不同的版本中生成需要很长时间:
结果为:
Version 1:
103-240:srcV1 michael$ time javac DelegatorVisitorI.java
real 0m1.859s
user 0m5.023s
sys 0m0.175s
Version 2:
103-240:srcV2 michael$ time javac DelegatorVisitorI.java
real 0m3.364s
user 0m7.713s
sys 0m0.342s
Version 3:
103-240:srcV3 michael$ time javac DelegatorVisitorI.java
real 2m58.009s
user 2m56.787s
sys 0m1.718s
Version 4:
103-240:srcV4 michael$ time javac DelegatorVisitorI.java
real 14m14.923s
user 14m3.738s
sys 0m5.141s
所有四个不同版本的 Java 文件具有相同的行为, 但由于代码重复,编译过程需要更长的时间。
另外有趣的是,如果你复制方法而不使用任何继承,那么编译是最快的,即使文件在很长的继承链之后变得更大。
(版本2和版本3的时间差大我个人无法理解,可能是javac编译器解析过程中的bug)