Давайте разберем набор примеров, чтобы увидеть, чем они отличаются. (Если вы используете 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
, добавьте, верните. Похоже на Яву.
Как насчет function
? На самом деле она не закрывает никаких данных, но это является анонимной функцией (называется Close$$anonfun$function$1
). Если игнорировать какую-либо специализацию, наибольший интерес представляют конструктор и применение:
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 вы делаете трюки с коробкой/распаковкой, а затем вызываете фактическую математику, то есть просто добавляете 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$
, а методы разделяются между ними таким образом, что это может быть не интуитивно понятно. Но apply определенно применяет вещи, и в целом вся система работает в значительной степени так, как вы ожидаете (после того, как вы сядете и очень долго обдумаете это).
person
Rex Kerr
schedule
19.04.2010
javap -c 'ClassName$$anonfun$etcetc'
для декомпиляции? - person Rex Kerr   schedule 20.04.2010