Vamos a desmontar un conjunto de ejemplos para que podamos ver cómo difieren. (Si se utiliza RC1, compilar con -no-specialization
para mantener las cosas más fáciles de entender.)
class Close {
var n = 5
def method(i: Int) = i+n
def function = (i: Int) => i+5
def closure = (i: Int) => i+n
def mixed(m: Int) = (i: Int) => i+m
}
En primer lugar, vamos a ver qué hace method
:
public int method(int);
Code:
0: iload_1
1: aload_0
2: invokevirtual #17; //Method n:()I
5: iadd
6: ireturn
bastante sencillo. Es un método. Cargue el parámetro, invoque el captador para n
, agregar, devolver. Se parece a Java.
¿Qué tal function
?En realidad, no cierra ningún dato, pero es una función anónima (llamada Close$$anonfun$function$1
). Si hacemos caso de cualquier especialidad, el constructor y se aplican son de mayor interés:
public scala.Function1 function();
Code:
0: new #34; //class Close$$anonfun$function$1
3: dup
4: aload_0
5: invokespecial #35; //Method Close$$anonfun$function$1."<init>":(LClose;)V
8: areturn
public Close$$anonfun$function$1(Close);
Code:
0: aload_0
1: invokespecial #43; //Method scala/runtime/AbstractFunction1."<init>":()V
4: return
public final java.lang.Object apply(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: invokestatic #26; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
5: invokevirtual #28; //Method apply:(I)I
8: invokestatic #32; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
11: areturn
public final int apply(int);
Code:
0: iload_1
1: iconst_5
2: iadd
3: ireturn
Así, se carga un puntero "this" y crea un nuevo objeto que toma la clase envolvente como su argumento. Esto es estándar para cualquier clase interna, realmente. La función no necesita hacer nada con la clase externa por lo que solo llama al constructor del super. Luego, cuando llame solicite, realice los trucos box/unbox y luego invoque los cálculos reales; es decir, simplemente agregue 5.
Pero, ¿qué sucede si utilizamos un cierre de la variable dentro de Close? La configuración es exactamente lo mismo, pero ahora el constructor Close$$anonfun$closure$1
se ve así:
public Close$$anonfun$closure$1(Close);
Code:
0: aload_1
1: ifnonnull 12
4: new #48; //class java/lang/NullPointerException
7: dup
8: invokespecial #50; //Method java/lang/NullPointerException."<init>":()V
11: athrow
12: aload_0
13: aload_1
14: putfield #18; //Field $outer:LClose;
17: aload_0
18: invokespecial #53; //Method scala/runtime/AbstractFunction1."<init>":()V
21: return
Es decir, se comprueba para asegurarse de que la entrada no es nulo (es decir, la clase externa no es nulo) y lo guarda en un campo. Ahora, cuando llega el momento de aplicarlo, después de que el boxeo/unboxing envoltorio:
public final int apply(int);
Code:
0: iload_1
1: aload_0
2: getfield #18; //Field $outer:LClose;
5: invokevirtual #24; //Method Close.n:()I
8: iadd
9: ireturn
ves que utiliza este campo para referirse a la clase padre, e invoca el captador de n
. Agregar, regresar, hecho. Por lo tanto, los cierres son bastante fáciles: el constructor de función anónima solo guarda la clase adjunta en un campo privado.
Ahora, ¿qué pasa si cerramos no una variable interna, pero un argumento de método? Eso es lo que hace Close$$anonfun$mixed$1
. En primer lugar, un vistazo a lo que hace el método mixed
:
public scala.Function1 mixed(int);
Code:
0: new #39; //class Close$$anonfun$mixed$1
3: dup
4: aload_0
5: iload_1
6: invokespecial #42; //Method Close$$anonfun$mixed$1."<init>":(LClose;I)V
9: areturn
Se carga el parámetro m
antes de llamar al constructor! Por lo tanto, no sorprende que el constructor tenga el siguiente aspecto:
public Close$$anonfun$mixed$1(Close, int);
Code:
0: aload_0
1: iload_2
2: putfield #18; //Field m$1:I
5: aload_0
6: invokespecial #43; //Method scala/runtime/AbstractFunction1."<init>":()V
9: return
donde ese parámetro se guarda en un campo privado. No se guarda ninguna referencia a la clase externa porque no la necesitamos. Y usted no debe sorprenderse al aplicar:
public final int apply(int);
Code:
0: iload_1
1: aload_0
2: getfield #18; //Field m$1:I
5: iadd
6: ireturn
Sí, solo cargamos ese campo almacenado y hacemos nuestras cuentas.
No estoy seguro de lo que estaba haciendo para no ver esto con su ejemplo: los objetos son un poco difíciles porque tienen las clases MyObject
y MyObject$
y los métodos se dividen entre los dos de una manera que puede no ser intuitivo. Pero la aplicación definitivamente aplica cosas, y en general todo el sistema funciona de la manera que uno esperaría (después de que te sientas y lo pienses realmente duro durante mucho tiempo).
¿Estás seguro de que estás utilizando 'javap -c 'ClassName $$ anonfun $ etcetc'' para hacer la descompilación? –