【问题标题】:Scala @specialized annotation infinite recursion?Scala @specialized注解无限递归?
【发布时间】:2016-12-26 09:20:50
【问题描述】:

版本: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

那么,如何重用带有专门注解的超类方法呢?

【问题讨论】:

  • 将Father 转换为特征似乎可以解决问题,但我不确定其原因。

标签: java scala annotations specialized-annotation


【解决方案1】:

来自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.

【讨论】:

  • 它只适用于 Scala 2.12(因为 trait 使用 Java 8 默认方法,处理方式不同),在 Scala 2.11 中,将其作为一个 trait 也会导致无限递归。
  • 我明白了。因此,除非您的特征是在 Scala 2.12 中转换的 SAM,否则这种情况会持续存在
  • 我认为traits修复它的原因是它们在静态助手中使用invokespecial而不是invokevirtual,它绕过了虚拟调用。理论上,对于普通类也可以通过在Father$mcI$sp::get 中使用invokespecial 来解决此问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-16
  • 2015-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-26
  • 1970-01-01
相关资源
最近更新 更多