web-dev-qa-db-fra.com

Utilisation de Scala traits avec les méthodes implémentées dans Java

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

59
Jus12

Répondre

De Java perspective Trait.scala Est compilé en Traitinterface. 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 = (!)

Comment est-il implémenté dans 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 classe abstraite Trait$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 interface
  • scalac 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.

Mélange de traits multiples

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

Traits multiples remplaçant la même méthode

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

Modifications empilables

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:

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

Les invocations de pile entières sont donc les suivantes:

  • méthode 'bar' sur l'instance FooOnSteroids (point d'entrée);
  • Méthode statique 'bar' de la classe Trait2 $ en passant cet argument et en renvoyant une concaténation de la méthode et de la chaîne 'Trait2 $$ super $ bar ()' et la chaîne ", Trait2";
  • 'Trait2 $$ super $ bar ()' sur l'instance FooOnSteroids qui appelle ...
  • Méthode statique 'bar' de la classe Trait1 $ en passant cet argument et renvoyant une concaténation de la méthode et de la chaîne 'Trait1 $$ super $ bar ()' et la chaîne ", Trait1";
  • 'Trait1 $$ super $ bar' sur l'instance FooOnSteroids qui appelle ...
  • méthode originale de "bar" de Foo

Et le résultat est "Foo, Trait1, Trait2".

Conclusion

Si vous avez réussi à tout lire, une réponse à la question d'origine se trouve dans les quatre premières lignes ...

130

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.

2
paradigmatic