Je suppose qu'il n'est pas possible d'invoquer des méthodes implémentées dans Scala traits de Java, ou existe-t-il un moyen?
Supposons que j'aie à Scala:
trait Trait {
def bar = {}
}
et en Java si je l'utilise comme
class Foo implements Trait {
}
Java se plaint que Trait is not abstract and does not override abstract method bar() in Trait
De Java perspective Trait.scala
Est compilé en Trait
interface. D'où l'implémentation de Trait
dans Java est interprété comme implémentant une interface - ce qui rend vos messages d'erreur évidents. Réponse courte: vous ne pouvez pas profiter des implémentations de traits en Java, car cela permettrait un héritage multiple dans Java = (!)
Réponse longue: comment ça marche à Scala? En regardant le bytecode/classes généré, on peut trouver le code suivant:
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
est une interfaceTrait$class
(ne pas confondre avec Trait.class
) est créée de manière transparente, ce qui techniquement fait pas implémenter l'interface Trait
. Cependant, il a une méthode static bar()
prenant comme argument Trait
(sorte de this
)Foo
implémente Trait
interfacescalac
implémente automatiquement les méthodes Trait
en déléguant à Trait$class
. Cela signifie essentiellement appeler Trait$class.bar(this)
.Notez que Trait$class
N'est ni membre de Foo
, ni Foo
ne l'étend. Il lui délègue simplement en passant this
.
Pour continuer la digression sur le fonctionnement de Scala ... Cela étant dit, il est facile d'imaginer comment le mélange de plusieurs traits fonctionne en dessous:
trait Trait1 {def ping(){}};
trait Trait2 {def pong(){}};
class Foo extends Trait1 with Trait2
se traduit par:
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
}
}
Maintenant, il est facile d'imaginer comment le mélange de plusieurs traits remplace la même méthode:
trait Trait {def bar(){}};
trait Trait1 extends Trait {override def bar(){}};
trait Trait2 extends Trait {override def bar(){}};
Trait1
Et Trait2
Redeviendront des interfaces étendant Trait
. Maintenant, si Trait2
Vient en dernier lors de la définition de Foo
:
class Foo extends Trait1 with Trait2
tu auras:
class Foo implements Trait1, Trait2 {
public void bar() {
Trait2$class.bar(this); //works because `this` implements Trait2
}
}
Cependant, la commutation de Trait1
Et Trait2
(Ce qui fera de Trait1
Le dernier) entraînera:
class Foo implements Trait2, Trait1 {
public void bar() {
Trait1$class.bar(this); //works because `this` implements Trait1
}
}
Considérez maintenant le fonctionnement des traits en tant que modifications empilables. Imaginez avoir une classe Foo vraiment utile:
class Foo {
def bar = "Foo"
}
que vous souhaitez enrichir de nouvelles fonctionnalités en utilisant des traits:
trait Trait1 extends Foo {
abstract override def bar = super.bar + ", Trait1"
}
trait Trait2 extends Foo {
abstract override def bar = super.bar + ", Trait2"
}
Voici le nouveau "Foo" sur les stéroïdes:
class FooOnSteroids extends Foo with Trait1 with Trait2
Cela se traduit par:
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";
}
}
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";
}
}
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);
}
}
Les invocations de pile entières sont donc les suivantes:
Et le résultat est "Foo, Trait1, Trait2".
Si vous avez réussi à tout lire, une réponse à la question d'origine se trouve dans les quatre premières lignes ...
Ce n'est en effet pas abstrait puisque bar
renvoie un Unit
(une sorte de NOP) vide. Essayer:
trait Trait {
def bar: Unit
}
Alors bar
sera une Java retournant void
.