Я в настоящее время смотрю на реализации закрытия на различных языках. Когда дело доходит до Scala, однако, я не могу найти любую документацию относительно того, как закрытие отображается на объектах Java.
Это хорошо документируется, что функции Scala отображаются на объектах FunctionN. Я предполагаю, что ссылка на свободную переменную закрытия должна быть сохранена где-нибудь в том функциональном объекте (поскольку это сделано в C++ 0x, например).
Я также пытался компилировать следующее с scalac и затем декомпилировать файлы класса с JD:
object ClosureExample extends Application {
def addN(n: Int) = (a: Int) => a + n
var add5 = addN(5)
println(add5(20))
}
В декомпилируемых источниках я вижу анонимный подтип Function1, который должен быть моим закрытием. Но применение () метод пуст, и анонимный класс не имеет никаких полей (который мог потенциально сохранить переменные закрытия). Я предполагаю, что декомпилятору не удалось вытащить интересную часть из файлов класса...
Теперь к вопросам:
Давайте разберем набор примеров, чтобы мы могли увидеть, чем они отличаются. (При использовании 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
}
Во-первых, давайте посмотрим, что делает метод
:
public int method(int);
Code:
0: iload_1
1: aload_0
2: invokevirtual #17; //Method n:()I
5: iadd
6: ireturn
Довольно просто. Это метод. Загрузите параметр, вызовите геттер для n
, добавьте, верните. Похоже на Java.
Как насчет функции
? На самом деле она не закрывает никакие данные, но является анонимной функцией (называемой 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, вы выполняете трюки с коробкой / распаковкой, а затем вызываете фактическую математику, то есть просто добавляете 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
. Сначала посмотрите, что делает смешанный
метод:
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
где этот параметр сохраняется в частном поле. Ссылка на внешний класс не сохраняется, потому что она нам не нужна. И вас не должно удивлять применение apply:
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 определенно применяет некоторые вещи, и в целом вся система работает примерно так, как вы ожидаете (после того, как вы сядете и очень долго обдумаете это).
В отличие от анонимных внутренних классов Java, которые являются псевдозамкнутыми и не могут изменять переменные, которые кажутся закрытыми в своей среде, Scala's замыкания реальны, поэтому код закрытия напрямую ссылается на значения в окружающей среде. Такие значения компилируются по-разному, когда на них ссылаются из закрытия, чтобы сделать это возможным (поскольку у кода метода нет способа получить доступ к локальным переменным из любых фреймов активации, кроме текущего).
Напротив, в Java их значения копируются в поля внутреннего класса, поэтому язык требует, чтобы исходные значения в окружающей среде были окончательными
, чтобы они никогда не расходились.
Поскольку все ссылки / замыкания функционального литерала Scala на значения во включающей среде находятся в коде метода функционального литерала apply ()
, они не отображаются как поля в фактическом Подкласс функции
, созданный для функционального литерала.
Я не знаю, как вы декомпилируете, но подробности того, как вы это сделали, вероятно, объясняют, почему вы не видите никакого кода для тела метода apply ()
.