Например, в Ruby "закрытие фактически продлит время жизни всех переменных, которые ему нужны. Оно не будет копировать их, но сохранит ссылку на них, и сами переменные не будут иметь права на сбор мусора (если язык содержит сбор мусора), а закрытие вокруг". [Скоркин]
Ответ 1
Закрытия в Scala также не содержат объектов с глубокой копией, они будут хранить ссылку только на объект. Более того, закрытие не получает его собственной лексической области, но вместо этого оно использует окружающий лексический охват.
class Cell(var x: Int)
var c = new Cell(1)
val f1 = () => c.x /* Create a closure that uses c */
def foo(e: Cell) = () => e.x
/* foo is a closure generator with its own scope */
val f2 = foo(c) /* Create another closure that uses c */
val d = c /* Alias c as d */
c = new Cell(10) /* Let c point to a new object */
d.x = d.x + 1 /* Increase d.x (i.e., the former c.x) */
println(f1()) /* Prints 10 */
println(f2()) /* Prints 2 */
Я не могу комментировать сборку мусора, но я предполагаю, что сборщик мусора JVM не будет удалять объекты, на которые ссылается закрытие, если закрытие все еще ссылается.
Ответ 2
У jvm нет замыканий, у него есть только объект. Компилятор scala генерирует анонимные классы, реализующие соответствующий признак функции (в зависимости от аргумента и типа результата подписи) для каждого появления замыкания в коде.
Например, если для некоторого l : List[Int]
вы пишете l.map(i => i + 1)
, он будет преобразован в
class SomeFreshName extends Function[Int, Int] {
def apply(i: Int) = i + 1
}
l.map(new SomeFreshName())
В этом случае нет никакого действительного замыкания, как в я = > я + 1, нет свободной переменной, а только аргумент я и константа.
Если вы закрываете некоторые локальные переменные или, что то же самое, параметр функции, они должны быть переданы как параметр конструктора в класс-замыкание:
для l.map(i => s + i)
, где s - строковый параметр или локальный метод, он будет делать
class SomeFreshName(s: String) extends Function[Int, String] {
def apply(i: Int) = s + i
}
l.map(new SomeFreshName(s))
передавая как можно больше параметров в конструкторе.
Примечание. Если s было полем класса вместо локального метода, тогда s + i
будет фактически this.s + i
, а this
будет передано анонимному классу.
В сборщике мусора нет ничего особенного (опять же, jvm не знает замыкания), просто, поскольку объект замыкания имеет ссылку на s, s будет жить как минимум до тех пор, пока объект закрытия.
Обратите внимание, что точно так же происходит на Java-языке с анонимными классами. Когда анонимный класс использует локали вложенного метода, эти локальные жители молча добавляют в качестве полей анонимных классов и передаются в конструкторе.
В java это разрешено только в том случае, если локальные значения final
, что эквивалентно scala val
, а не var
.
В самом деле, с этой реализацией, как только создается замыкание, у нее есть своя копия переменной, которая закрывает другую. Если он изменяет их, эта модификация не будет отражена в методе. Если они изменены в закрытии, это не будет отражено в методе.
Предположим, вы пишете
var i = 0
l.foreach{a => println(i + ": " + a); i = i + 1}
println("There are " + i + " elements in the list")
Реализация, описанная ранее, будет
class SomeFreshName(var i: Int) extends Int => Unit {
def apply(a: Int) = println(i + ": " + a); i = i + 1
}
var i = 0
l.foreach(new SomeFreshName(i)
println("There are " + i + " elements in the list")
Таким образом, было бы две переменные i
, одна из которых была бы в методе, а другая - в SomeFreshName
. Только тот, который был в SomeFreshName, будет изменен, а последний println всегда будет сообщать 0 элементов.
Scala решить свою проблему, заменив var, сделанный в замыкании ссылочными объектами. Учитывая класс
class Ref[A](var content: A)
код сначала заменяется на
val iRef = new Ref[Int](0)
l.foreach{a =>
println(iRef.content + ": " + a);
iRef.content += iRef.content + 1
}
println("There are " + i + " elements in the list")
Это делается, конечно, только для var, который происходит с помощью замыкания, а не для каждого var. При этом var был заменен на val, фактическое значение переменной было перемещено в кучу. Теперь закрытие можно сделать как обычно, и оно работает
class SomeFreshName(iRef: Ref[Int]) ...