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
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 (!)
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 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
.
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 } }
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 } }
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:
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); } }
Entonces todas las invocaciones de stack son las siguientes:
Y el resultado es “Foo, Trait1, Trait2”.
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
.