Immutable lists can be misleading. Here I will use Kotlin. This snippet is viable on Java, C#, Swift... too.
I'm assuming you understand why immutable objects are "more robust" to manipulate data.
Here a "book" with "pages".
data class Book(
val pages: List<String>
)
Developers using your class can create books and get pages. The val
means that pages
is "final" so developers cannot change the pages
reference from an instance of book
.
// Developer 1
val pages = ArrayList<String>()
pages.add("Super page 1")
pages.add("Another super page, that's the 2")
val book = Book(pages)
No problem with that, you cannot do that
book.pages = ArrayList<String>() // Impossible
but here come the problems.
Nothing can stop developers to do that
// Developer 1
val pages = ArrayList<String>()
pages.add("Super page 1")
pages.add("Another super page, that's the 2")
val book = Book(pages)
// Developer 2
pages.clear()
after that, your book instance is empty even if you though your object was immutable: playground.
Nothing can stop developers to do that
// Developer 1
val pages = ArrayList<String>()
pages.add("Super page 1")
pages.add("Another super page, that's the 2")
val book = Book(pages)
// Developer 2
(book.pages as ArrayList).clear()
after that, your book instance is empty even if you though your object was immutable: playground.
To solve that, multiple solutions (non exhausive)
- Use
Collections.unmodifiableList(java.util.List<? extends T>)
- Use
org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:X.X.X
- Do extra copies on constructor and on getter
Perso, to avoid extra library, I love to do that playground
class Book private constructor(
private val pages: List<String>
) {
// Plain old get function instead of Kotlin getter syntax to help clients auto completion "get..."
fun getPages(): List<String> {
return ArrayList(pages)
}
companion object {
fun create(pages: List<String>): Book {
return Book(ArrayList(pages))
}
}
}
PS: Illustrated with lists, but same "issue" with Map...