Давайте разберем несколько примеров, чтобы мы могли увидеть, как они отличаются. (При использовании RC1, скомпилируйте с -no-specialization
, чтобы было легче понять.)
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
}
Сначала давайте посмотрим, что делает method
:
public int method(int);
Code:
0: iload_1
1: aload_0
2: invokevirtual #17; //Method n:()I
5: iadd
6: ireturn
Довольно просто. Это метод. Загрузите параметр, вызовите метод получения для n
, добавьте, верните. Выглядит так же, как Java.
Как насчет function
? На самом деле он не закрывает никаких данных, но является анонимной функцией (называемой Close$$anonfun$function$1
). Если мы игнорируем какую-либо специализацию, наиболее интересны конструктор и apply:
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
Итак, вы загружаете указатель "this" и создаете новый объект, который принимает включающий класс в качестве аргумента. Это стандартно для любого внутреннего класса, правда. Функция не должна ничего делать с внешним классом, поэтому она просто вызывает конструктор super. Затем, при вызове apply, вы делаете трюки box / unbox и затем вызываете фактическую математику - то есть просто добавляете 5.
Но что, если мы используем закрытие переменной внутри Close? Настройка точно такая же, но теперь конструктор Close$$anonfun$closure$1
выглядит так:
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
То есть он проверяет, что входные данные не равны NULL (то есть внешний класс не равен NULL), и сохраняет их в поле. Теперь, когда придет время применить его, после упаковки / распаковки:
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
вы видите, что оно использует это поле для ссылки на родительский класс и вызывает метод получения для n
. Добавить, вернуть, готово. Итак, замыкания достаточно просты: конструктор анонимной функции просто сохраняет включающий класс в закрытом поле.
А что если закрыть не внутреннюю переменную, а аргумент метода? Вот что делает Close$$anonfun$mixed$1
. Сначала посмотрим, что делает метод 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
Загружает параметр m
перед вызовом конструктора! Поэтому неудивительно, что конструктор выглядит так:
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
где этот параметр сохраняется в приватном поле. Ссылка на внешний класс не сохраняется, потому что она нам не нужна. И вы не должны удивляться, применяя либо:
public final int apply(int);
Code:
0: iload_1
1: aload_0
2: getfield #18; //Field m$1:I
5: iadd
6: ireturn
Да, мы просто загружаем это сохраненное поле и делаем математику.
Я не уверен, что вы делали, чтобы не видеть это в вашем примере - объекты немного хитры, потому что они имеют классы MyObject
и MyObject$
, а методы разделяются между ними таким образом, что не может быть интуитивным Но применение определенно применимо, и в целом вся система работает почти так, как вы ожидаете (после того, как вы усердно и долго думаете об этом очень долго).