mirror of
https://github.com/corda/corda.git
synced 2025-01-18 02:39:51 +00:00
DOCS: Serialization roundtrip removes mutability explanation (#2888)
This commit is contained in:
parent
36f02ff4b4
commit
6c9a39ae44
@ -442,6 +442,85 @@ associates it with the actual member variable.
|
||||
fun getStatesToConsume() = states
|
||||
}
|
||||
|
||||
Mutable Containers
|
||||
``````````````````
|
||||
|
||||
Because Java fundamentally provides no mechanism by which the mutability of a class can be determined this presents a
|
||||
problem for the serialization framework. When reconstituting objects with container properties (lists, maps, etc) we
|
||||
must chose whether to create mutable or immutable objects. Given the restrictions, we have decided it is better to
|
||||
preserve the immutability of immutable objects rather than force mutability on presumed immutable objects.
|
||||
|
||||
.. note:: Whilst we could potentially infer mutability empirically, doing so exhaustively is impossible as it's a design
|
||||
decision rather than something intrinsic to the JVM. At present, we defer to simply making things immutable on reconstruction
|
||||
with the following workarounds provided for those who use them. In future, this may change, but for now use the following
|
||||
examples as a guide.
|
||||
|
||||
For example, consider the following:
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
data class C(val l : MutableList<String>)
|
||||
|
||||
val bytes = C(mutableListOf ("a", "b", "c")).serialize()
|
||||
val newC = bytes.deserialize()
|
||||
|
||||
newC.l.add("d")
|
||||
|
||||
The call to ``newC.l.add`` will throw an ``UnsupportedOperationException``.
|
||||
|
||||
There are several workarounds that can be used to preserve mutability on reconstituted objects. Firstly, if the class
|
||||
isn't a Kotlin data class and thus isn't restricted by having to have a primary constructor.
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class C {
|
||||
val l : MutableList<String>
|
||||
|
||||
@Suppress("Unused")
|
||||
constructor (l : MutableList<String>) {
|
||||
this.l = l.toMutableList()
|
||||
}
|
||||
}
|
||||
|
||||
val bytes = C(mutableListOf ("a", "b", "c")).serialize()
|
||||
val newC = bytes.deserialize()
|
||||
|
||||
// This time this call will succeed
|
||||
newC.l.add("d")
|
||||
|
||||
Secondly, if the class is a Kotlin data class, a secondary constructor can be used.
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
data class C (val l : MutableList<String>){
|
||||
@ConstructorForDeserialization
|
||||
@Suppress("Unused")
|
||||
constructor (l : Collection<String>) : this (l.toMutableList())
|
||||
}
|
||||
|
||||
val bytes = C(mutableListOf ("a", "b", "c")).serialize()
|
||||
val newC = bytes.deserialize()
|
||||
|
||||
// This will also work
|
||||
newC.l.add("d")
|
||||
|
||||
Thirdly, to preserve immutability of objects (a recommend design principle - Copy on Write semantics) then mutating the
|
||||
contents of the class can be done by creating a new copy of the data class with the altered list passed (in this example)
|
||||
passed in as the Constructor parameter.
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
data class C(val l : List<String>)
|
||||
|
||||
val bytes = C(listOf ("a", "b", "c")).serialize()
|
||||
val newC = bytes.deserialize()
|
||||
|
||||
val newC2 = newC.copy (l = (newC.l + "d"))
|
||||
|
||||
.. note:: If mutability isn't an issue at all then in the case of data classes a single constructor can
|
||||
be used by making the property var instead of val and in the ``init`` block reassigning the property
|
||||
to a mutable instance
|
||||
|
||||
Enums
|
||||
`````
|
||||
|
||||
|
@ -0,0 +1,60 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import net.corda.core.serialization.ConstructorForDeserialization
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.Test
|
||||
|
||||
class RoundTripTests {
|
||||
@Test
|
||||
fun mutableBecomesImmutable() {
|
||||
data class C(val l : MutableList<String>)
|
||||
val factory = testDefaultFactoryNoEvolution()
|
||||
val bytes = SerializationOutput(factory).serialize(C(mutableListOf ("a", "b", "c")))
|
||||
val newC = DeserializationInput(factory).deserialize(bytes)
|
||||
|
||||
Assertions.assertThatThrownBy {
|
||||
newC.l.add("d")
|
||||
}.isInstanceOf(UnsupportedOperationException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mutableStillMutable() {
|
||||
class C {
|
||||
val l : MutableList<String>
|
||||
|
||||
@Suppress("Unused")
|
||||
constructor (l : MutableList<String>) {
|
||||
this.l = l.toMutableList()
|
||||
}
|
||||
}
|
||||
val factory = testDefaultFactoryNoEvolution()
|
||||
val bytes = SerializationOutput(factory).serialize(C(mutableListOf ("a", "b", "c")))
|
||||
val newC = DeserializationInput(factory).deserialize(bytes)
|
||||
|
||||
newC.l.add("d")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mutableStillMutable2() {
|
||||
data class C (val l : MutableList<String>){
|
||||
@ConstructorForDeserialization
|
||||
@Suppress("Unused")
|
||||
constructor (l : Collection<String>) : this (l.toMutableList())
|
||||
}
|
||||
|
||||
val factory = testDefaultFactoryNoEvolution()
|
||||
val bytes = SerializationOutput(factory).serialize(C(mutableListOf ("a", "b", "c")))
|
||||
val newC = DeserializationInput(factory).deserialize(bytes)
|
||||
|
||||
newC.l.add("d")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mutableBecomesImmutable4() {
|
||||
data class C(val l : List<String>)
|
||||
val factory = testDefaultFactoryNoEvolution()
|
||||
val bytes = SerializationOutput(factory).serialize(C(listOf ("a", "b", "c")))
|
||||
val newC = DeserializationInput(factory).deserialize(bytes)
|
||||
val newC2 = newC.copy (l = (newC.l + "d"))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user