Utilisation des traits Scala avec les méthodes implémentées en Java

Je suppose qu’il n’est pas possible d’invoquer des méthodes implémentées dans les traits Scala à partir de Java, ou existe-t-il un moyen?

Supposons que j’aie en 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

Répondre

Du sharepoint vue Java, Trait.scala est compilé dans l’ interface Trait . Ainsi, implémenter 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 tirer parti des implémentations de traits en Java, car cela permettrait l’inheritance multiple en Java (!)

Comment est-il mis en œuvre à Scala?

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 interface
  • La Trait$class abstract Trait$class (ne pas confondre avec Trait.class ) est créée de manière transparente, ce qui, techniquement, Trait.class pas l’ interface Trait . Cependant, il a une méthode de static bar() prenant l’instance Trait en argument (en quelque sorte)
  • Foo implémente l’interface Trait
  • scalac implémente automatiquement les méthodes Trait en déléguant à la Trait$class . Cela signifie essentiellement appeler Trait$class.bar(this) .

Notez que Trait$class n’est ni membre de Foo , ni Foo étend. Il y délègue simplement en passant this .

Mélange de plusieurs traits

Poursuivre la digression sur le fonctionnement de Scala … Cela étant dit, il est facile d’imaginer comment le mélange de traits multiples 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 } } 

Caractères multiples remplaçant la même méthode

Maintenant, il est facile d’imaginer comment le mélange de plusieurs caractères se substituant à la même méthode:

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

Là encore, Trait1 et Trait2 deviendront des interfaces d’extension de Trait . Maintenant, si Trait2 arrive en dernier en définissant 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, si vous Trait1 et Trait2 (rendant Trait1 en dernier), vous obtiendrez:

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

Modifications empilables

Considérons maintenant comment fonctionnent les modifications empilables. Imaginez avoir une classe vraiment utile Foo:

 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:

Trait1

 interface Trait1 { Ssortingng Trait1$$super$bar(); Ssortingng bar(); } abstract class Trait1$class { public static Ssortingng 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 { Ssortingng Trait2$$super$bar(); Ssortingng bar(); } public abstract class Trait2$class { public static Ssortingng 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 Ssortingng Trait1$$super$bar() { // call superclass 'bar' method version return Foo.bar(); } public final Ssortingng Trait2$$super$bar() { return Trait1$class.bar(this); } public Ssortingng bar() { return Trait2$class.bar(this); } } 

Les invocations de la stack sont donc les suivantes:

  • méthode ‘bar’ sur l’instance FooOnSteroids (point d’entrée);
  • La méthode statique ‘bar’ de Trait2 $ class transmettant ceci comme argument et renvoyant une concaténation de l’appel de méthode ‘Trait2 $$ super $ bar ()’ et de la chaîne “, Trait2”;
  • ‘Trait2 $$ super $ bar ()’ sur l’instance FooOnSteroids qui appelle …
  • Méthode statique ‘bar’ de Trait1 $ class transmettant cet argument et renvoyant une concaténation de l’appel de méthode ‘Trait1 $$ super $ bar ()’ et de la chaîne “, Trait1”;
  • ‘Trait1 $$ super $ bar’ sur l’instance FooOnSteroids qui appelle …
  • la méthode originale du «bar» de Foo

Et le résultat est “Foo, Trait1, Trait2”.

Conclusion

Si vous avez tout lu, une réponse à la question initiale se trouve dans les quatre premières lignes …

En effet, ce n’est pas abstrait puisque la bar retourne une Unit vide (une sorte de NOP). Essayer:

 trait Trait { def bar: Unit } 

Ensuite, la bar sera une méthode abstraite Java renvoyant le void .