Current Situation
Java has supported immutable record types since Java 14, and Kotlin's data classes can easily implement immutable classes, even supporting copy functions. However, they are designed for simple immutable objects, and have problems dealing with complex deep data structure.
The reason why deep, complex immutable objects are difficult to handle is not how to create a brand new data structure from scratch, which is very simple for any programming language. The real difficulty is wanting to make some local adjustments to an existing data structure and build a new data structure. This is very difficult for current Java and Kotlin, please see
First define an immutable tree node
- Java
- Kotlin
record TreeNode(
String name,
List<TreeNode> childNodes
) {}
data class TreeNode(
val name: String,
val childNodes: List<TreeNode>
)
Prepare an old object
- Java
- Kotlin
var oldTreeNode = ...blabla...
val oldTreeNode = ...blabla...
Let's implement three data change operations in order from simple to complex to create a brand new object
-
Change the name property of the root node
- Java
- Kotlin
TreeNode newTreeNode = new TreeNode(
"Hello", // Set root node name
oldTreeNode.childNodes()
);val newTreeNode = oldTreeNode.copy(
name = "Hello" // Set root node name
);This case is very simple, and the problems of Java/Kotlin are not obvious yet.
-
Change the name property of the first level child node
Breadcrumb conditions:
- Position of first level child node: pos1
- Java
- Kotlin
TreeNode newTreeNode = new TreeNode(
oldTreeNode.name(),
IntStream
.range(0, oldTreeNode.childNodes().size())
.mapToObj(index1 -> {
TreeNode oldChild1 = oldTreeNode.childNodes().get(index1);
if (index1 != pos1) {
return oldChild1;
}
return new TreeNode(
"Hello", // Set name of first level child node
oldChild1.childNodes()
);
})
.toList()
);val newTreeNode = oldTreeNode.copy(
childNodes = oldTreeNode
.childNodes
.mapIndexed { index1, child1 ->
if (index1 == pos1) {
child1.copy(
name = "Hello" // Set name of first level child node
)
} else {
child1
}
}
) -
Change the name property of the second level child node
Breadcrumb conditions:
- Position of first level child node: pos1
- Position of second level child node: pos2
- Java
- Kotlin
TreeNode newTreeNode = new TreeNode(
oldTreeNode.name(),
IntStream
.range(0, oldTreeNode.childNodes().size())
.mapToObj(index1 -> {
TreeNode oldChild1 = oldTreeNode.childNodes().get(index1);
if (index1 != pos1) {
return oldChild1;
}
return new TreeNode(
oldChild1.name(),
IntStream
.range(0, oldChild1.childNodes().size())
.mapToObj(index2 -> {
TreeNode oldChild2 = oldChild1.childNodes().get(index2);
if (index2 != pos2) {
return oldChild2;
} else {
return new TreeNode(
"Hello", // Set name of second level child node
oldChild2.childNodes()
);
}
})
.toList()
);
})
.toList()
);val newTreeNode = oldTreeNode.copy(
childNodes = oldTreeNode
.childNodes
.mapIndexed { index1, child1 ->
if (index1 == pos1) {
child1.copy(
childNodes = child1
.childNodes
.mapIndexed { index2, child2 ->
if (index2 == pos2) {
child2.copy(
name = "Hello" // Set name of second level child node
)
} else {
child2
}
}
)
} else {
child1
}
}
)
It can be seen that as long as the object tree has some depth, creating a new immutable object based on another immutable object (i.e. secondary "modification") will be a nightmare.