Использование Scala черт с реализованными методами в Java

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

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

trait Trait {
  def bar = {}
}

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

class Foo implements Trait {
}

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

Ответ 1

Ответ

С точки зрения 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 (точка входа);
  • Статический метод Trait2 $class 'bar', передающий это как аргумент и возвращающий конкатенацию вызова и строки метода Trait2 $$ super $bar() ', Trait2 ";
  • 'Trait2 $$ super $bar()' в экземпляре FooOnSteroids, который вызывает...
  • Статический метод Trait1 $class 'bar', передающий это как аргумент и возвращающий конкатенацию вызова и строки метода Trait1 $$ super $bar() ', Trait1 ";
  • 'Trait1 $$ super $bar' в экземпляре FooOnSteroids, который вызывает...
  • оригинальный метод Foo 'bar'

И результат: "Foo, Trait1, Trait2".

Заключение

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

Ответ 2

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

trait Trait {
  def bar: Unit
}

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