Descompilar el código de Scala: ¿por qué hay dos métodos anulados en la clase derivada?
class A
{
private var str: String = "A"
val x: A = this
override def toString(): String = str
def m1(other: AnyRef): AnyRef = {
println("This is A.m1(AnyRef)")
other
}
}
class B extends A {
private var str: String = "B"
var z: Int = 0
override val x: B = this
override def m1(other: AnyRef): B = {
println("This is B.m1(AnyRef)")
this
}
}
La clase B del código anterior se descompila como:
public class test$B extends test$A {
private java.lang.String str;
private int z;
private final test$B x;
private java.lang.String str();
private void str_$eq(java.lang.String);
public int z();
public void z_$eq(int);
public test$B x();
public test$B m1(java.lang.Object);
public java.lang.Object m1(java.lang.Object);
public test$A x();
public test$B();
}
No puedo entender por qué hay dos "versiones" del método m1
en el código descompilado. Según tengo entendido, B.m1
simplemente anula A.m1
y public java.lang.Object m1(java.lang.Object)
pertenece a A
y no debería estar en clase B
.
2 respuestas
Este es un método de puente sintético.
En el código de bytes de Java, los métodos solo anulan los métodos con la misma firma exacta. Si B no tuviera ninguna instancia de Object m1(Object)
, entonces cualquier intento de llamarlo llamaría a la implementación en A, que no es lo que desea. Por lo tanto, el compilador inserta un método de puente sintético que simplemente llama a B m1(Object)
. Este comportamiento no es específico de Scala, también ocurre en Java puro.
Puede verlo con más detalle examinando el desmontaje. Si compilo y desmonto el siguiente código
class A
{
def m1(other: AnyRef): AnyRef = {
println("This is A.m1(AnyRef)")
other
}
}
class B extends A {
override def m1(other: AnyRef): B = {
println("This is B.m1(AnyRef)")
this
}
}
Las partes relevantes de B son
.method public m1 : (Ljava/lang/Object;)LB;
.code stack 2 locals 2
L0: getstatic Field scala/Predef$ MODULE$ Lscala/Predef$;
L3: ldc 'This is B.m1(AnyRef)'
L5: invokevirtual Method scala/Predef$ println (Ljava/lang/Object;)V
L8: aload_0
L9: areturn
L10:
.end code
.methodparameters
other final
.end methodparameters
.end method
.method public bridge synthetic m1 : (Ljava/lang/Object;)Ljava/lang/Object;
.code stack 2 locals 2
L0: aload_0
L1: aload_1
L2: invokevirtual Method B m1 (Ljava/lang/Object;)LB;
L5: areturn
L6:
.end code
.methodparameters
other final
.end methodparameters
.end method
Como puede ver, el método m1 (Ljava/lang/Object;)Ljava/lang/Object;
simplemente envía los argumentos a m1 (Ljava/lang/Object;)LB;
.
Ves dos métodos porque uno es "el real" y el segundo es un método puente generado para admitir tipos de retorno covariantes .
Desde JavaDoc para Class.getMethod:
mientras que el lenguaje Java prohíbe que una clase declare múltiples métodos con la misma firma pero diferentes tipos de retorno, la máquina virtual Java no lo hace. Esta mayor flexibilidad en la máquina virtual se puede utilizar para implementar varias funciones de lenguaje. Por ejemplo, retornos covariantes se pueden implementar con métodos puente ; el método puente y el método que se anula tendrían la misma firma pero diferentes tipos de retorno.
El método de puente tendrá indicadores ACC_BRIDGE y ACC_SYNTHETICS establecido. En realidad, esto no tiene nada que ver con Scala, como puede ver fácilmente si compila las dos clases siguientes:
class A {
public Object m1(int i) { return i; }
}
class B extends A {
@Override public String m1(int a) { return "hey " + a; }
}
Si ahora usa javap -v
para descompilar B.class
, verá las diferentes banderas de los métodos:
public java.lang.String m1(int);
descriptor: (I)Ljava/lang/String;
flags: ACC_PUBLIC
[...some lines omitted...]
public java.lang.Object m1(int);
descriptor: (I)Ljava/lang/Object;
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Preguntas relacionadas
Nuevas preguntas
scala
Scala es un lenguaje de programación de propósito general dirigido principalmente a la máquina virtual Java. Diseñado para expresar patrones de programación comunes de una manera concisa, elegante y segura de tipos, fusiona estilos de programación imperativos y funcionales. Sus características clave son: un sistema de tipo estático avanzado con inferencia de tipo; tipos de funciones; la coincidencia de patrones; parámetros implícitos y conversiones; sobrecarga del operador; interoperabilidad total con Java; concurrencia