来自Quirks of Scala Specialization的文章:
避免超级调用
合格的超级调用(可能从根本上)被专业化破坏了。在专业化阶段正确地重新布线超级访问器方法是迄今为止尚未解决的噩梦。所以,像瘟疫一样避开它们,至少现在是这样。特别是,可堆叠的修改模式不能很好地使用它。
所以这很可能是一个编译器错误,一般来说你不应该使用带有 Scala 专业化的 super 调用。
经过一番调查:
javap -c Son.class
public class Son extends Father$mcI$sp {
public int get(int);
Code:
0: aload_0
1: iload_1
2: invokevirtual #14 // Method get$mcI$sp:(I)I
5: ireturn
public int get$mcI$sp(int);
Code:
0: getstatic #23 // Field scala/Predef$.MODULE$:Lscala/Predef$;
3: ldc #25 // String Son.get
5: invokevirtual #29 // Method scala/Predef$.println:(Ljava/lang/Object;)V
8: aload_0
9: iload_1
10: invokespecial #31 // Method Father$mcI$sp.get:(I)I
13: ireturn
Son.get(int) 调用Son.get$mcI$sp(int) 变成Father$mcI$sp.get(int):
javap -c Father\$mcI\$sp.class
public class Father$mcI$sp extends Father<java.lang.Object> {
public int get(int);
Code:
0: aload_0
1: iload_1
2: invokevirtual #12 // Method get$mcI$sp:(I)I
5: ireturn
public int get$mcI$sp(int);
Code:
0: iload_1
1: ireturn
看起来我们找到了原因 - Father$mcI$sp.get(int) 对 get$mcI$sp 进行了虚拟调用,在 Son 中超载!这就是导致这里无限递归的原因。
编译器必须创建方法get 的专用版本,即get$mcI$sp,以支持Father[T] 的非专用泛型版本,不幸的是,这使得super 无法调用专用类.
现在将 Father 更改为 trait 后会发生什么(使用 Scala 2.12):
javap -c Son.class
public class Son implements Father$mcI$sp {
public int get$mcI$sp(int);
Code:
0: getstatic #25 // Field scala/Predef$.MODULE$:Lscala/Predef$;
3: ldc #27 // String Son.get
5: invokevirtual #31 // Method scala/Predef$.println:(Ljava/lang/Object;)V
8: aload_0
9: iload_1
10: invokestatic #37 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
13: invokestatic #43 // InterfaceMethod Father.get$:(LFather;Ljava/lang/Object;)Ljava/lang/Object;
16: invokestatic #47 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
19: ireturn
看起来不像在父类中调用get$mcI$sp,而是调用静态方法Father.get$:
javap -c Father.class
public interface Father<A> {
public static java.lang.Object get$(Father, java.lang.Object);
Code:
0: aload_0
1: aload_1
2: invokespecial #17 // InterfaceMethod get:(Ljava/lang/Object;)Ljava/lang/Object;
5: areturn
public A get(A);
Code:
0: aload_1
1: areturn
public static int get$mcI$sp$(Father, int);
Code:
0: aload_0
1: iload_1
2: invokespecial #26 // InterfaceMethod get$mcI$sp:(I)I
5: ireturn
public int get$mcI$sp(int);
Code:
0: aload_0
1: iload_1
2: invokestatic #33 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
5: invokeinterface #17, 2 // InterfaceMethod get:(Ljava/lang/Object;)Ljava/lang/Object;
10: invokestatic #37 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
13: ireturn
这里有趣的是,get 方法似乎没有得到真正的专业化,因为它必须将 get$mcI$sp 中的值装箱,这可能是一个错误,或者可能是 Scala 中删除了对特征的专业化支持2.12.