Ответ 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".
Заключение
Если вам удалось прочитать все, ответ на исходный вопрос будет в первых четырех строках...