Из статьи Quirks of Scala Specialization:
Избегайте супер звонки
Квалифицированные супер звонки (возможно, принципиально) сломана со специализацией. Повторное использование супер-аксессорных методов на этапе специализации - это кошмар, который до сих пор не решен. Поэтому избегайте их, как чумы, по крайней мере на данный момент. В частности, шаблон стекируемых модификаций не будет работать с ним хорошо.
Так что, скорее всего, ошибка компилятора, в общем, вы не должны использовать super
звонки с Scala специализации.
После небольшого исследования:
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
быть признаком (с 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
не получает реальной специализации, так как он s, чтобы установить значение в get$mcI$sp
, что может быть ошибкой, или, возможно, поддержка специализации для признаков была сброшена в Scala 2.12.
Превращение «Отца» в черту, похоже, решает проблему, но я не уверен в причине этого. – adamwy