2016-12-26 55 views
2

版本:斯卡拉2.11.8Scala @specialized注释无限递归?

我定义在继承特殊类型和覆盖方法的类:

class Father[@specialized(Int) A]{ 
    def get(from: A): A = from 
} 

class Son extends Father[Int]{ 
    override def get(from: Int): Int = { 
    println("Son.get") 
    super.get(from) 
    } 
} 

new Son().get(1) // will cause infinite recursion 

那么,如何重用超类的专门注释的方法?

+0

把'Father'变成特质似乎解决了这个问题,但我不确定这个原因。 – adamwy

回答

3

从文章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中可能会丢失对特征的专业化支持。

+0

“特质”的哪些属性使这项工作成为可能? –

+0

它使得它仅适用于Scala 2.12(因为特征使用了不同处理的Java 8默认方法),因此在Scala 2.11中使它成为一个特性也会导致无限递归。 – adamwy

+0

我明白了。所以这种情况会持续下去,除非你的特质在Scala中转换为SAM 2.12 –