What immutable means
A Kotlin String is immutable: once it exists, none of its characters can be changed, added or removed. Every operation that seems to modify a string, such as joining, slicing or replacing, actually builds a new string and leaves the old one as it was.
Reading a character is fine, using square brackets with an index. Writing one is not:
val message: String = "Jello world"
message[0] = 'H'
This code does not compile. The compiler reports:
error: no 'set' operator method providing array access.
Square brackets on the left of = would need a set function on String, and String has none. It has a get function, which is why message[0] works for reading.
The variable is not the string
Immutable strings do not mean a variable can never hold different text. There are two separate things:
- the string, a block of characters in memory, which never changes;
- the variable, a name that refers to one string at a time.
Declared with var, the variable can be pointed at a different string:
var message: String = "Jello world"
message = "Hello world"
println(message)
Hello world
Nothing inside "Jello world" was edited. A second string, "Hello world", was created and message now refers to it.
val versus var
Two different restrictions are easy to mix up. val stops you from reassigning the variable. Immutability stops you from editing characters. For a string, the character-editing line fails in both cases, but reassignment works only with var:
val a = "Jello"
a = "Hello" // error: 'val' cannot be reassigned
var b = "Jello"
b = "Hello" // fine
The exercise below uses var for this reason.
The exercise
The starter code holds a typo. The variable name contains "bndrei Chiriac", and the line name[0] = 'A' tries to fix the first letter, but it does not compile for the reason above.
The fix works in two moves:
- Build the corrected text. Take everything after the wrong first character with
name.substring(1), which gives"ndrei Chiriac", and put the right letter in front with+. Joining two strings with+creates a new string. - Assign it back. Because
namewas declared withvar, it can now refer to the new string.
var name: String = "bndrei Chiriac"
// Change code below:
name = "A" + name.substring(1)
After that line, name holds "Andrei Chiriac". The old string, with the typo, is no longer referred to by anything.
Other ways to get a modified copy
The same idea, build a new string and keep it, applies to every change:
val s = "Jello"
println(s.replaceRange(0, 1, "H")) // Hello
println(s.replace('J', 'H')) // Hello
println(s.uppercase()) // JELLO
println(s) // Jello
The last line shows that s never changed. If you want the new text, you have to catch the return value, either in a new variable or by assigning to a var.
Common mistakes
- Calling a method and ignoring the result. Writing
name.replace('b', 'A')on its own line does nothing useful, because the new string is thrown away andnamekeeps its old value. - Declaring the variable with
valand then reassigning. The compiler rejects it. Usevarwhen the variable must point to new text. - Building a long string character by character in a loop with
+. Every+creates a new string, which copies all the previous characters. For many small additions, aStringBuilderis the tool made for editing text in place, thentoString()gives the final string. - Thinking the length changed. The slicing and joining in the exercise leave the length the same, because one character was replaced by one character. The point is that the result is a different string, not an edited one.
Key takeaways
- Strings are immutable:
text[i] = 'x'does not compile. - String operations return new strings; the original stays the same.
- A
varcan be reassigned to a new string, while avalcannot. - To "change" one character, join the parts around it with the new character, for example
"A" + name.substring(1).