Usar rasgos de Scala con métodos implementados en Java

Supongo que no es posible invocar métodos implementados en rasgos de Scala desde Java, ¿o hay alguna manera?

Supongamos que tengo en Scala:

trait Trait { def bar = {} } 

y en Java si lo uso como

 class Foo implements Trait { } 

Java se queja de que Trait is not abstract and does not override abstract method bar() in Trait

Responder

Desde la perspectiva de Java, Trait.scala se comstack en la interfaz de Trait . Por lo tanto, la implementación de Trait en Java se interpreta como la implementación de una interfaz, lo que hace que sus mensajes de error sean obvios. Respuesta corta: no puede aprovechar las implementaciones de rasgos en Java, porque esto habilitaría la herencia múltiple en Java (!)

¿Cómo se implementa en Scala?

Respuesta larga: entonces, ¿cómo funciona en Scala? Mirando el bytecode / classes generado uno puede encontrar el siguiente código:

 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 es una interfaz
  • Resumen La Trait$class (no confundir con Trait.class ) se crea de forma transparente, lo que técnicamente no implementa la interfaz Trait . Sin embargo, tiene un método static bar() que toma la instancia de Trait como argumento (tipo de this )
  • Foo implementa la interfaz Trait
  • scalac implementa automáticamente los métodos de Trait delegando en Trait$class . Esto básicamente significa llamar a Trait$class.bar(this) .

Tenga en cuenta que Trait$class no es miembro de Foo , ni Foo extiende. Simplemente lo delega pasando this .

Mezclar en múltiples rasgos

Para continuar la digresión sobre cómo funciona Scala … Dicho esto, es fácil imaginar cómo funciona la mezcla en múltiples rasgos debajo:

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

se traduce a:

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

Múltiples rasgos que reemplazan el mismo método

Ahora es fácil imaginar cómo la mezcla en múltiples rasgos anula el mismo método:

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

De nuevo, Trait1 y Trait2 se convertirán en interfaces que extenderán Trait . Ahora si Trait2 es el último al definir Foo :

 class Foo extends Trait1 with Trait2 

obtendrás:

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

Sin embargo, el cambio de Trait1 y Trait2 (haciendo que Trait1 sea ​​el último) dará como resultado:

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

Modificaciones astackbles

Ahora considere cómo funcionan los rasgos como modificaciones astackbles. Imagina tener una clase Foo realmente útil:

 class Foo { def bar = "Foo" } 

que desea enriquecer con alguna nueva funcionalidad que use rasgos:

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

Aquí está el nuevo ‘Foo’ con esteroides:

 class FooOnSteroids extends Foo with Trait1 with Trait2 

Se traduce a:

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

Entonces todas las invocaciones de stack son las siguientes:

  • método ‘bar’ en la instancia de FooOnSteroids (punto de entrada);
  • El método estático ‘barra’ de Trait2 $ class pasa esto como argumento y devuelve una concatenación de la llamada y cadena de caracteres ‘Trait2 $$ super $ bar ()’ “, Trait2”;
  • ‘Trait2 $$ super $ bar ()’ en la instancia de FooOnSteroids que llama …
  • El método estático ‘barra’ de Trait1 $ class pasa esto como argumento y devuelve una concatenación de la llamada y cadena de caracteres ‘Trait1 $$ super $ bar ()’ “, Trait1”;
  • ‘Trait1 $$ super $ bar’ en la instancia FooOnSteroids que llama …
  • método original de ‘barra’ de Foo

Y el resultado es “Foo, Trait1, Trait2”.

Conclusión

Si ha logrado leer todo, la respuesta a la pregunta original está en las primeras cuatro líneas …

De hecho, no es abstracto ya que la bar está devolviendo una Unit vacía (un tipo de NOP). Tratar:

 trait Trait { def bar: Unit } 

Entonces la bar será un método abstracto de Java que devuelve void .