Давайте разберем набор примеров, чтобы мы могли видеть, как они отличаются. (При использовании 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
). Если мы будем игнорировать любой специализации, конструктор и применять наиболее интересны:
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
Итак, вы загрузили «это» указатель и создать новый объект, который принимает класс ограждающую в качестве аргумента. Это действительно стандарт для любого внутреннего класса. Функция не должна ничего делать с внешним классом, поэтому она просто вызывает конструктор супер. Затем, когда вы набираете заявку, вы делаете тэки 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
То есть, он проверяет, чтобы убедиться, что вход не равен нулю (то есть внешний класс не равен нулю) и сохраняет его в поле. Теперь, когда приходит время, чтобы применить его, после того, как бокс/распаковки обертки:
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 final int apply(int);
Code:
0: iload_1
1: aload_0
2: getfield #18; //Field m$1:I
5: iadd
6: ireturn
Да, мы просто загрузить сохраненную поле и сделать нашу математику.
Я не уверен, что вы делали, чтобы не видеть это с помощью вашего примера. Объекты немного сложны, потому что у них есть классы MyObject
и MyObject$
, и методы получают разделение между двумя способами, которые могут не быть интуитивный. Но применять определенно применяет вещи, и в целом вся система работает в значительной степени так, как вы ожидали (после того, как вы сядете и подумаете об этом очень тяжело в течение очень долгого времени).
Вы действительно используете 'javap -c 'ClassName $$ anonfun $ etcetc'' для декомпиляции? –