Использование черт Scala с реализованными методами в Java - PullRequest
59 голосов
/ 03 октября 2011

Полагаю, невозможно вызвать методы, реализованные в чертах Scala, из Java, или есть способ?

Предположим, у меня в Scala:

trait Trait {
  def bar = {}
}

и в Java, если я использую его как

class Foo implements Trait {
}

Ява жалуется, что Trait is not abstract and does not override abstract method bar() in Trait

Ответы [ 2 ]

130 голосов
/ 03 октября 2011

Ответ

С точки зрения Java Trait.scala компилируется в Trait интерфейс . Следовательно, реализация Trait в Java интерпретируется как реализация интерфейса, что делает ваши сообщения об ошибках очевидными. Короткий ответ: вы не можете воспользоваться преимуществами реализации признаков в Java, потому что это позволило бы множественное наследование в 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 это интерфейс
  • abstract Trait$class (не путайте с Trait.class) класс создается прозрачно, что технически не реализует интерфейс Trait. Однако у него есть static bar() метод, принимающий Trait экземпляр в качестве аргумента (вроде this)
  • Foo реализует Trait интерфейс
  • scalac автоматически реализует Trait методы путем делегирования Trait$class. По сути, это означает, что звонить 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(){}};

Снова Trait1 и Trait2 станут интерфейсами, расширяющими 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
    }
}

Однако переключение Trait1 и Trait2 (делая 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

Это переводится как:

Trait1

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";
  }
}

Trait2

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";
  }
}

FooOnSteroids

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);
  }      
}

Итак, весь вызов стека выглядит следующим образом:

  • метод 'bar' в экземпляре FooOnSteroids (точка входа);
  • Статический метод 'bar' класса Trait2 $, передающий его в качестве аргумента и возвращающий объединение вызова метода Trait2 $$ super $ bar () и строки ", Trait2";
  • 'Trait2 $$ super $ bar ()' в экземпляре FooOnSteroids, который вызывает ...
  • Статический метод 'bar' класса Trait1 $, передающий его в качестве аргумента и возвращающий объединение вызова метода Trait1 $$ super $ bar () и строки ", Trait1";
  • 'Trait1 $$ super $ bar' в экземпляре FooOnSteroids, который вызывает ...
  • оригинальный метод 'бара' Фу

И результат «Foo, Trait1, Trait2».

Заключение

Если вам удалось все прочитать, ответ на оригинальный вопрос находится в первых четырех строках ...

2 голосов
/ 03 октября 2011

Это действительно не абстрактно, поскольку bar возвращает пустое Unit (разновидность NOP). Попробуйте:

trait Trait {
  def bar: Unit
}

Тогда bar будет абстрактным методом Java, возвращающим void.

...