为什么Scala使用反射来调用结构类型上的方法?

17

如果函数接受结构类型,可以定义为:

def doTheThings(duck: { def walk; def quack }) { duck.quack }
或者
type DuckType = { def walk; def quack  }
def doTheThings(duck: DuckType) { duck.quack }

然后,您可以按以下方式使用该函数:

class Dog {
    def walk { println("Dog walk") }
    def quack { println("Dog quacks") }
}

def main(args: Array[String]) {
    doTheThings(new Dog);
}
如果您反编译(到Java)由scalac为我的示例生成的类,您可以看到doTheThings的参数是Object类型,实现使用反射调用参数上的方法(即 duck.quack)。 我的问题是为什么要使用反射?难道不能只使用匿名和invokevirtual来代替反射吗?这是实现结构类型调用的一种方式(针对我的示例)(Java语法,但重点是字节码)。
class DuckyDogTest {
  interface DuckType {
    void walk();
    void quack();
  }

  static void doTheThing(DuckType d) {
    d.quack();
  }

  static class Dog {
    public void walk() { System.out.println("Dog walk"); }
    public void quack() { System.out.println("Dog quack"); }
  }

  public static void main(String[] args) {
    final Dog d = new Dog();
    doTheThing(new DuckType() {
      public final void walk() { d.walk(); }
      public final void quack() { d.quack();}
    });
  }
}
2个回答

15

考虑一个简单的命题:

type T = { def quack(): Unit; def walk(): Unit }
def f(a: T, b: T) = 
  if (a eq b) println("They are the same duck!")
  else        println("Different ducks")

f(x, x) // x is a duck

根据你的提议,它将打印出“不同的鸭子”。你可以进一步完善它,但是你不能使用代理来保持引用相等。可能的解决方案是使用类型类模式,但这需要传递另一个参数(即使是隐式的)。尽管如此,它更快。但这主要是因为Java反射速度的低效。希望方法句柄能解决速度问题。不幸的是,Scala在一段时间内不会放弃Java 5、6和7(它们没有方法句柄)...

我不理解最后一句话,请你解释一下好吗? - om-nom-nom
@om-nom-nom invokevirtual 在 JVM 1.5 和 1.6 上不存在,因此 Scala 不能依赖它。Scala 2.10 实际上会弃用 JVM 1.5,但在 Scala 能够利用仅存在于 JVM 1.7 上的功能之前还需要一些时间。 - Daniel C. Sobral
@Daniel C. Sobral:我猜你在最后一条评论中想说的是invokedynamic而不是invokevirtual - Op De Cirkel
1
@OpDeCirkel 你说得对。使用invokedynamic将是一个替代方案,不会像问题提出的解决方案那样遭受身份丢失的问题,但这取决于不支持Java 1.5/1.6。这已经不再是天方夜谭了--社区就是否要求Scala 2.11需要Java 7进行了投票。我不知道结果如何,但这是现在正在考虑的事情。 - Daniel C. Sobral
2
投票结果决定暂时保留1.6版本,并添加实验性支持1.7代码生成,有可能直接跳过1.7版本,在Scala的下一个版本2.11之后直接升级到1.8。 - Alex Zuzin

10

除了代理对象在结构类型上实现方法之外,它还需要适当的“通过”实现所有Any上的方法(equals、hashCode、toString、isInstanceOf、asInstanceOf)和AnyRef上的方法(getClass、wait、notify、notifyAll和synchronized)。虽然其中一些很简单,但有些几乎不可能做到完全正确。特别是,列出的所有方法都是AnyRef上的“final”(为了Java兼容性和安全性),因此无法由您的代理对象正确地实现。


1
@Daniel C. Sobral 和 Dave Griffith:你们两位的回答都是可以接受的。所以我不得不抛硬币来正式接受一个答案。 - Op De Cirkel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接