Utilizzando i tratti Scala con i metodi implementati in Java

Immagino che non sia ansible invocare metodi implementati nei tratti Scala da Java, o c’è un modo?

Supponiamo di avere in Scala:

trait Trait { def bar = {} } 

e in Java se lo uso come

 class Foo implements Trait { } 

Java lamenta che Trait is not abstract and does not override abstract method bar() in Trait

    Risposta

    Dal punto di vista di Java, Trait.scala è compilato nell’interfaccia di Trait . Quindi l’implementazione di Trait in Java viene interpretata come implementazione di un’interfaccia, il che rende i messaggi di errore ovvi. Risposta breve: non è ansible sfruttare le implementazioni dei tratti in Java, poiché ciò consentirebbe l’ereditarietà multipla in Java (!)

    Com’è implementato in Scala?

    Risposta lunga: come funziona in Scala? Guardando il bytecode / classs generato si può trovare il seguente codice:

     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 è un’interfaccia
    • La Trait$class astratta Trait$class (non confondere con Trait.class ) viene creata in modo trasparente, che tecnicamente non implementa l’interfaccia di Trait . Tuttavia ha un metodo static bar() che prende l’istanza di Trait come argomento (una specie di)
    • Foo implementa l’interfaccia di Trait
    • scalac implementa automaticamente i metodi Trait delegando a Trait$class . Questo essenzialmente significa chiamare Trait$class.bar(this) .

    Nota che Trait$class non è un membro di Foo , né lo proroga. Li delega semplicemente passando this .

    Mescolando in più tratti

    Per continuare la divagazione su come funziona Scala … Detto questo, è facile immaginare come funzioni la miscelazione in più tratti:

     trait Trait1 {def ping(){}}; trait Trait2 {def pong(){}}; class Foo extends Trait1 with Trait2 

    si traduce in:

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

    Tratti multipli che sovrascrivono lo stesso metodo

    Ora è facile immaginare come mescolare in più tratti che prevalgono sullo stesso metodo:

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

    Di nuovo Trait1 e Trait2 diventeranno interfacce che estendono i Trait . Ora se il Trait2 arriva per ultimo quando si definisce Foo :

     class Foo extends Trait1 with Trait2 

    otterrai:

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

    Tuttavia, il passaggio da Trait1 e Trait2 (rendendo Trait1 l’ultimo) risulterà in:

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

    Modifiche impilabili

    Ora considera come i tratti funzionano come modifiche impilabili. Immagina di avere una class Foo davvero utile:

     class Foo { def bar = "Foo" } 

    che vuoi arricchire con alcune nuove funzionalità utilizzando i tratti:

     trait Trait1 extends Foo { abstract override def bar = super.bar + ", Trait1" } trait Trait2 extends Foo { abstract override def bar = super.bar + ", Trait2" } 

    Ecco il nuovo ‘Foo’ sugli steroidi:

     class FooOnSteroids extends Foo with Trait1 with Trait2 

    Si traduce in:

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

    Quindi le invocazioni dell’intero stack sono le seguenti:

    • metodo ‘bar’ sull’istanza di FooOnSteroids (punto di ingresso);
    • Il metodo statico ‘bar’ della class $ di Trait2 lo passa come argomento e restituisce una concatenazione di chiamata e stringa “metodo Trait2 $$ super $ bar ()”, Trait2 “;
    • ‘Trait2 $$ super $ bar ()’ sull’istanza di FooOnSteroids che chiama …
    • Il metodo statico ‘bar’ di Trait1 $ class lo passa come argomento e restituisce una concatenazione di chiamata e stringa “metodo Trait1 $$ super $ bar ()”, Trait1 “;
    • ‘Trait1 $$ super $ bar’ sull’istanza di FooOnSteroids che chiama …
    • metodo “bar” originale di Foo

    E il risultato è “Foo, Trait1, Trait2”.

    Conclusione

    Se sei riuscito a leggere tutto, una risposta alla domanda originale è nelle prime quattro righe …

    In effetti non è astratto dato che la bar restituisce un’unità vuota (una sorta di NOP). Provare:

     trait Trait { def bar: Unit } 

    Quindi la bar sarà un metodo astratto Java che restituisce il void .