java - Scala @specialized注解无限递归?

原文 标签 java scala annotations specialized-annotation

版本:scala 2.11.8

我在继承中定义了一个具有特殊类型和重写方法的类:

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


那么,如何重用带有特殊注释的超类方法呢?

最佳答案

从文章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方法没有得到真正的专业化,因为它必须将get$mcI$sp中的值装箱,这可能是一个错误,或者可能是Scala 2.12中放弃了对特性的专业化支持。

相关文章:

java - 测试硒詹金斯

java - 在Eclipse中更改方法签名时添加注释

scala - 如何在Spark窗口函数中以降序使用orderby()?

scala - 将Scala long转换为字符串会打印空白

java - 注释方法以便以后选择

java - 用@PostConstruct标记的方法何时调用?

java - 在匿名类上使用方法

java - Hibernate Criteria从单个查询获得多个rowCounts

java - 使用Room Persistence在运行时生成查询

scala - 如何开发宏以使null短路?