【问题标题】:Using Scala traits with implemented methods in Java在 Java 中将 Scala 特征与已实现的方法一起使用
【发布时间】:2011-10-03 16:22:37
【问题描述】:

我想不可能从 Java 调用 Scala 特征中实现的方法,或者有没有办法?

假设我在 Scala 中:

trait Trait {
  def bar = {}
}

如果我将它用作 Java 的话

class Foo implements Trait {
}

Java 抱怨 Trait is not abstract and does not override abstract method bar() in Trait

【问题讨论】:

  • 这是一个错误吗?该行不应该看起来像“Java 抱怨 Foo 不是抽象的,并且不会覆盖 Trait 中的抽象方法 bar()”吗?

标签: java scala scala-java-interop


【解决方案1】:

回答

从 Java 的角度来看,Trait.scala 被编译成 Trait 接口。因此,在 Java 中实现 Trait 被解释为实现了一个接口——这使您的错误消息显而易见。简短的回答:你不能利用 Java 中的 trait 实现,因为这会在 Java 中启用多重继承(!)

它是如何在 Scala 中实现的?

长答案:那么它在 Scala 中是如何工作的?查看生成的字节码/类可以找到以下代码:

interface Trait {
    void bar();
}

abstract class Trait$class {
    public static void bar(Trait thiz) {/*trait implementation*/}
}

class Foo implements Trait {
    public void bar() {
        Trait$class.bar(this);  //works because `this` implements Trait
    }
}
  • Trait是一个接口
  • 抽象Trait$class(不要与Trait.class混淆)类是透明创建的,从技术上讲,它实现Trait接口。但是它确实有一个 static bar() 方法,将 Trait 实例作为参数(类似于 this
  • Foo 实现Trait 接口
  • scalac 通过委托给Trait$class 自动实现Trait 方法。这实质上意味着调用Trait$class.bar(this)

注意Trait$class 既不是Foo 的成员,也不是Foo 扩展它。它只是通过传递this 来委托给它。

混合多种特征

继续题外话 Scala 是如何工作的……话虽如此,很容易想象在下面混合多个特征是如何工作的:

trait Trait1 {def ping(){}};
trait Trait2 {def pong(){}};
class Foo extends Trait1 with Trait2

翻译为:

class Foo implements Trait1, Trait2 {
  public void ping() {
    Trait1$class.ping(this);    //works because `this` implements Trait1
  }

  public void pong() {
    Trait2$class.pong(this);    //works because `this` implements Trait2
  }
}

覆盖相同方法的多个特征

现在很容易想象如何混合多个特征来覆盖相同的方法:

trait Trait {def bar(){}};
trait Trait1 extends Trait {override def bar(){}};
trait Trait2 extends Trait {override def bar(){}};

Trait1Trait2 将再次成为扩展 Trait 的接口。现在如果 Trait2 在定义 Foo 时出现在最后:

class Foo extends Trait1 with Trait2

你会得到:

class Foo implements Trait1, Trait2 {
    public void bar() {
        Trait2$class.bar(this); //works because `this` implements Trait2
    }
}

但是切换Trait1Trait2(使Trait1 成为最后一个)将导致:

class Foo implements Trait2, Trait1 {
    public void bar() {
        Trait1$class.bar(this); //works because `this` implements Trait1
    }
}

可堆叠修改

现在考虑作为可堆叠修改的特征是如何工作的。想象一下有一个非常有用的类 Foo:

class Foo {
  def bar = "Foo"
}

您想使用特征来丰富一些新功能:

trait Trait1 extends Foo {
  abstract override def bar = super.bar + ", Trait1"
}

trait Trait2 extends Foo {
  abstract override def bar = super.bar + ", Trait2"
}

这是类固醇的新“Foo”:

class FooOnSteroids extends Foo with Trait1 with Trait2

翻译成:

特征1

interface Trait1 {
  String Trait1$$super$bar();
  String bar();
}
abstract class Trait1$class {
  public static String bar(Trait1 thiz) {
    // interface call Trait1$$super$bar() is possible
    // since FooOnSteroids implements Trait1 (see below)
    return thiz.Trait1$$super$bar() + ", Trait1";
  }
}

特质2

public interface Trait2 {
  String Trait2$$super$bar();
  String bar();
}
public abstract class Trait2$class {
  public static String bar(Trait2 thiz) {
    // interface call Trait2$$super$bar() is possible
    // since FooOnSteroids implements Trait2 (see below)
    return thiz.Trait2$$super$bar() + ", Trait2";
  }
}

FooOn类固醇

class FooOnSteroids extends Foo implements Trait1, Trait2 {
  public final String Trait1$$super$bar() {
    // call superclass 'bar' method version
    return Foo.bar();
  }

  public final String Trait2$$super$bar() {
    return Trait1$class.bar(this);
  }

  public String bar() {
    return Trait2$class.bar(this);
  }      
}

所以整个堆栈调用如下:

  • FooOnSteroids 实例上的'bar' 方法(入口点);
  • Trait2$class 的 'bar' 静态方法将 this 作为参数传递并返回 'Trait2$$super$bar()' 方法调用和字符串 ", Trait2" 的串联;
  • 'Trait2$$super$bar()' 在 FooOnSteroids 实例上调用 ...
  • Trait1$class 的 'bar' 静态方法将 this 作为参数传递并返回 'Trait1$$super$bar()' 方法调用和字符串 ", Trait1" 的串联;
  • 'Trait1$$super$bar' 在 FooOnSteroids 实例上调用 ...
  • 原始 Foo 的 'bar' 方法

结果是“Foo, Trait1, Trait2”。

结论

如果您已设法阅读所有内容,则原始问题的答案在前四行...

【讨论】:

  • 嗯......它缺少abstract override 的工作方式,但它在其他方面非常好。 :-)
  • 谢谢!还有一个错字:当你打算切换 Trait1 和 Trait2 时,你又写了class Foo implements Trait1, Trait2
  • @KajMagnus:这部分是 Tvaroh 添加的,也许他可以修复它?
  • @TomaszNurkiewicz 抱歉,我看错了答案。我认为它是正确的。 (没注意到相关代码sn-p其实是Java代码而不是Scala代码)。
  • +1 作为编译器开发人员(和爱好者),这些信息真的很有帮助。现在我不需要自己对其进行逆向工程。非常感谢! :3
【解决方案2】:

它确实不是抽象的,因为bar 返回一个空的Unit(一种 NOP)。试试:

trait Trait {
  def bar: Unit
}

那么bar 将是一个返回void 的Java 抽象方法。

【讨论】:

  • 这是我的问题。如果我在barTrait 中实现了一个复杂的方法,我想在Java 类中重用它,就像在Scala 中一样。但我想这是不可能的。唯一的解决方法似乎是将 Trait 转换为抽象类。
  • 是的。你应该把它变成一个抽象类。或者更好的是,将其保留为特征,但添加一个仅扩展它的抽象类(以具有 Java 绑定)。然后你可以从 Scala 扩展 trait 或从 Java 继承抽象类。
  • 是的,尽管这会占用 Java 抽象类的唯一可用空间。
  • 其他解决方案是使用组合/聚合。您可以使用生成的静态方法(请参阅 Tomasz 答案)或编写适合 Java 交互的对象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-09
  • 2014-03-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多