What slicing is
Slicing means cutting a run of consecutive characters out of a string. In Kotlin the basic tool is substring(startIndex, endIndex). It returns a new string made of the characters from startIndex up to, but not including, endIndex. Both indexes are zero-based, like the indexes you use to read a single character.
val language = "JavaScript"
val piece = language.substring(2, 6)
println(piece) // vaSc
println(language) // JavaScript
The character at index 2 is v and it is included. The character at index 6 is r and it is not. The result has endIndex - startIndex characters, here 4, which makes it easy to check your arithmetic.
The original string is untouched. Kotlin strings are immutable, so substring always builds a new String and leaves the old one as it was.
Leaving out the end index
When you pass only the start index, the slice runs to the end of the string:
val language = "JavaScript"
println(language.substring(4)) // Script
This form is the natural choice for "everything from here on". Combined with length, it also takes characters from the end: text.substring(text.length - 5) is the last five characters.
The half-open range
The start is included and the end is excluded. This is called a half-open range, and it has two useful properties:
- The length of the slice is
endIndex - startIndex, with no plus or minus one to remember. - Slices fit together.
substring(0, 6)andsubstring(6)cover the string exactly, with no gap and no overlap.
An empty slice is allowed: "Coding".substring(3, 3) is an empty string.
The exercise
The lesson gives a variable message and asks for three slices. Count the characters of "Coding feels great" first:
- indexes 0 to 5 are
Coding; - index 6 is a space;
- indexes 7 to 11 are
feels; - index 12 is a space;
- indexes 13 to 17 are
great.
Now each step in turn:
- The first 6 characters. They sit at indexes 0 to 5, so the end index is 6, one past the last character you want.
- The 8th through 12th characters. People count from 1, Kotlin counts from 0, so these are indexes 7 to 11. The end index is 12.
- The last 5 characters. They start at index 13 and run to the end, so the one-argument form is enough.
val message = "Coding feels great"
val firstWord = message.substring(0, 6)
val middleWord = message.substring(7, 12)
val lastWord = message.substring(13)
Each variable is declared with val because it is assigned once and never changes. Printing them gives:
Coding
feels
great
Other ways to slice
Kotlin's standard library has shorter helpers for the common cases:
val language = "JavaScript"
println(language.take(4)) // Java
println(language.takeLast(6)) // Script
println(language.drop(4)) // Script
println(language.substring(2..5)) // vaSc
take(n) keeps the first n characters and drop(n) removes them. takeLast(n) keeps the last n. A range such as 2..5 is inclusive at both ends, so substring(2..5) is the same slice as substring(2, 6). The helpers do not throw when n is larger than the string: "abc".take(10) is just "abc".
Common mistakes
- Off-by-one on the end index. Writing
substring(0, 5)for the first six characters loses the last one. The end index is the position after the slice. - Counting from 1. The "8th character" is at index 7.
- Going out of bounds. An end index larger than the length, a start index larger than the length, or a start larger than the end all throw a
StringIndexOutOfBoundsException. For"JavaScript", which has 10 characters,substring(4, 20)fails withRange [4, 20) out of bounds for length 10. - Forgetting to keep the result.
message.substring(0, 6)on its own line produces a string that nobody uses. Store it in a variable or print it. - Expecting the original to change. After slicing,
messagestill holds the whole text.
Key takeaways
substring(start, end)includesstartand excludesend.substring(start)runs to the end of the string.- The slice length is
end - start. - Slicing returns a new string; the original never changes.
- Indexes outside
0..lengththrow an exception.