DOCS: Serialization roundtrip removes mutability explanation (#2888)

This commit is contained in:
Katelyn Baker 2018-03-28 09:40:21 +01:00 committed by GitHub
parent 36f02ff4b4
commit 6c9a39ae44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 139 additions and 0 deletions

View File

@ -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
`````

View File

@ -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"))
}
}