I want to set up two values that hold immutable references to each other. Example:
data class Person(val other: Person)
val jack = Person(jill), jill = Person(jack) // doesn't compile
Note: lateinit
doesn't seem to work with data class primary constructors.
Any ideas?
You could get away with something like this:
class Person() {
private var _other: Person? = null
private constructor(_other: Person? = null) : this() {
this._other = _other
}
val other: Person
get() {
if (_other == null) {
_other = Person(this)
}
return _other ?: throw AssertionError("Set to null by another thread")
}
}
And then you would be able to do:
val jack = Person()
val jill = jack.other
Using a data class
here does not work for multiple reasons:
First because a data class
can't have an empty constructor.
Even if that wasn't a problem, the generated methods would end up having a cyclic dependency and will fail in runtime with java.lang.StackOverflowError
. So you'd have to overwrite toString
, equals
, etc. which kind of defeats the purpose of using data class
in the first place.
Here is the trick (note, this is really a trick, you need a good reason to use it in real code).
Unfortunately it won't work with data classes, as they seem to be secured against this kind of hacks.
But if you have java-stile classes, you may use two things to your advantage:
val
s in the constructor (same as with final
in java) this
inside the constructor (and you may leak it outside if you really want)Which means that you can create another Person
inside the constructor of the first person and finalize the creation of both classes before the constructor finishes.
Once again: exposing this
as I did below is a bad idea. When otherFactory
is called, it's parameter is only half-initialized. This may lead to nasty bugs, especially if you try to publish such reference in multithreaded environment.
A bit safer approach is to create both Persons inside the constructor of the first Person (you'll need to supply the fields of both entities as arguments). It's safer because you're in control of the code that uses half-initialized this
reference.
class Person {
val name: String
val other: Person
constructor(name: String, other: Person) {
this.name = name
this.other = other
}
// !! not very safe !!
constructor(name: String, otherFactory: (Person) -> Person) {
this.other = otherFactory(this)
this.name = name
}
// a bit safer
constructor(name: String, otherName: String) {
this.other = Person(otherName, this)
this.name = name
}
}
val person1 = Person("first") {
Person("second", it)
}
val person2 = person1.other
print(person1.name) // first
print(person2.name) // second
val person3 = Person("third", "fourth")
val person4 = person3.other
print(person3.name)
print(person4.name)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With