Ich verwende ein System, das viele Objekte unter Verwendung von Transaktionen initialisieren muss, und aus Gründen, die über den Rahmen dieser Frage hinausgehen, müssen diese Transaktionen in die Konstruktoren übergeben werden. So:
trait Mutable
class Txn(i: Int) {
def newID(implicit m: Mutable): Int = i
override def finalize(): Unit = println("Finalisiert " + i)
}
class User(t0: Txn) extends Mutable {
val id = t0.newID(this)
}
Jetzt fürchte ich, dass es ein Problem mit dem Garbage Collector der Transaktionen gibt:
val u = new User(new Txn(1234))
System.gc() // hmmm, es scheint nichts zu passieren?
Also lautet meine Frage: Wird das t0
Konstruktorargument jemals vom Garbage Collector eingesammelt, oder erschaffe ich hier ein Memory-Leak? In einem äquivalenten Java-Code hätte ich wohl so etwas:
public class User implements Mutable {
final int id;
public User(Txn t0) {
id = t0.newID(this);
}
}
und ich bin sicher, dass t0
eingesammelt wird. Aber ist das im Fall von Scala wahr?
Wenn nicht, wie kann ich sicherstellen, dass t0
eingesammelt wird? Denken Sie daran, dass ich die Transaktion als Konstruktorargument übergeben muss, weil die User
-Klasse einige Traits implementiert, die in die Methoden von Txn
übergeben werden müssen, sodass diese Methoden (wie newID
) nicht aufgerufen werden können, bevor User
konstruiert wird.
Ich habe bereits versucht, alles außerhalb des Benutzerobjekts zu konstruieren, mit tonnenweise verzögerten, voneinander abhängigen Werten, aber das war wirklich unordentlich. Beispielsweise führt dies, was bereits halbwegs unleserlich ist, zu einem Stackoverflow:
trait User extends Mutable { def id: Int }
def newUser(implicit tx: Txn): User = {
lazy val _id: Int = tx.newID(u)
lazy val u = new User { val id: Int = _id } // ups, sollte lazy val id sein!
u
}
val u = newUser(new Txn(1234))
Man kann sich vorstellen, wie ärgerlich es ist, dass der Compiler das Problem mit dem fehlenden lazy val hier nicht erkennt, daher würde ich definitiv die Konstruktorargumentvariante bevorzugen.