java - icon - jlabel definicion
Usar rasgos de Scala con métodos implementados en Java (2)
Responder
Desde la perspectiva de Java, Trait.scala
se compila 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 conTrait.class
) se crea de forma transparente, lo que técnicamente no implementa la interfazTrait
. Sin embargo, tiene un métodostatic bar()
que toma la instancia deTrait
como argumento (tipo dethis
) -
Foo
implementa la interfazTrait
-
scalac
implementa automáticamente los métodos deTrait
delegando enTrait$class
. Esto básicamente significa llamar aTrait$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 apilables
Ahora considere cómo funcionan los rasgos como modificaciones apilables. 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 pila 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 ...
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
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
.