This blog post accompanies the first episode of our YouTube series "Kotlin Standard Library Safari", which you can find on our official Kotlin YouTube channel, or watch here directly!
What's Kotlin Standard Library Safari?
In the "Kotlin Standard Library Safari" series, we’re going through the useful functionality the standard library in Kotlin has to offer, one subject at a time. In the process, we’re hopefully going to unearth some hidden gems together, which will come in handy the next time you write Kotlin code. Because if you know how to wield it, the Kotlin standard library is a powerful tool which can help you be more productive solving your problems, and be more expressive in your code.
This episode is all about strings – how we manipulate them, extract information, compare them, and much more. Let's get going!
Creating strings
Strings are one of the most prevalent data types that you’re probably familiar with. After all, all kinds of information get stored in the form of text strings, one way or another. Probably even in your first ever Kotlin program, you had a string literal saying “Hello, World”, or something similar:
println("Hello, World!") // Hello, World!
But, as you may know, even string literals can do a bit more in Kotlin than just statically storing some text.
With string interpolation (also called "string templates"), we can enrich our strings by referencing variables, calling functions, or even evaluating complex expressions:
val name = "Johnathan"
println("Hello, $name!") // Hello, Johnathan
println("Your name is ${name.count()} long!") // Your name is 9 long!
When our strings contain multiple lines of text or special characters that are usually reserved, like quotation marks or backslashes, we can use multiline strings, which are denoted by triple-quotes. Everything will still behave as expected:
val name = """
Johnathan,
The Great,
The "Knowledgeable"
""".trimIndent()
println("Hello, $name!")
println("Your name is ${name.count()} long!")
/* Prints:
Hello, Johnathan,
The Great,
The "Knowledgeable"!
Your name is 41 long!
*/
When you type out these triple-quoted "raw" strings in IntelliJ IDEA, you can notice that a call to trimIndent()
is automatically added to the string. This function removes the common indent of all input lines, and also removes the first and the last lines if they are blank. This means that if we have some XML or JSON stored in a string, for example, we can keep its nice formatting without having to worry that we might introduce additional characters. Note how, in this example, the printed output is not indented, and doesn't begin or end with an unnecessary empty line:
fun main() {
val myJson = """
{
"name": "jane",
"lastname": "doe",
"age": 29
}
""".trimIndent()
println(myJson)
}
/* Prints:
{
"name": "jane",
"lastname": "doe",
"age": 29
}
*/
If you are worried about performance when invoking an extra function during string creation, fear not. For constant strings, this transformation is evaluated at compile time with no runtime overhead since Kotlin 1.3.40.
Of course, there are tons of other places where strings could come from. For example, Kotlin can read terminal input via the readLine()
function. Or, we could read text from a file, to name just two examples:
val fromStdIn = readLine()
val fromFile = File("input.txt").readText()
Another neat way of creating Kotlin strings yourself is via the buildString
function. We can give this function a code block which populates a StringBuilder, which offers us functions like append
and appendLine
. This is particularly useful if you are crafting large strings, and performance is a concern.
val name = "Jane"
val myString = buildString {
repeat(10) {
append("Hello, ")
append(name)
appendLine("!")
}
}
println(myString)
/* Prints:
Hello, Jane!
Hello, Jane!
Hello, Jane!
Hello, Jane!
Hello, Jane!
Hello, Jane!
Hello, Jane!
Hello, Jane!
Hello, Jane!
Hello, Jane!
*/
And there is a lot we can do with all these strings! Next to some functionality like upper- and lowercasing a string, Kotlin also comes with some functions that are particularly useful when we want to extract information out of them.
Extracting information from strings
Extracting information usually means removing anything that isn’t useful. For example, we might want to detect strings that don’t contain any real information at all – so empty strings, that have no characters in them, or blank strings, that only contain whitespace.
To check whether we have this kind of string, we can use the isBlank
and isEmpty
functions:
println(" ".isBlank()) // true
println("".isEmpty()) // true
We can also conveniently replace those empty and blank strings with default values via the ifBlank
and ifEmpty
functions. Here, we can add a block which generates a default value for our strings if the corresponding condition is met:
val neverBlankString = " ".ifBlank {
"Never blank!"
}
println(neverBlankString) // Never blank!
When we have a string with meaningful content, but that information is surrounded by whitespace we can use the trim
function to remove empty space from the beginning and end of our string, like so:
val input = " valuable info "
println(input.trim()) // valuable info
If there are some other characters or maybe a common phrase which we don’t really care about on the sides of the text, there’s three more functions which are our allies: removePrefix
, removeSuffix
, and removeSurrounding
. With those, we can get rid of characters in the front, the back, and enclosing the text respectively:
val input = "##placeholder##"
println(input.removePrefix("##")) // placeholder##
println(input.removeSuffix("##")) // ##placeholder
println(input.removeSurrounding("##")) // placeholder
Another big way of extracting information from strings is via regular expressions, or short regexes. But this is a bit of a bigger topic, so those will have to wait for a future episode.
Comparing strings
Now that we know some tricks of how to get strings containing only the information we really care about, we can do things like compare those strings with each other.
Equality checks are easy enough, with the double-equals sign checking whether two strings are identical:
val stringA = "astring"
if(stringA == "astring") {
println("Everything cool!")
}
// Everything cool!
But did you know that you can also compare two strings based on their alphabetical order? This can be done by using the less-than and greater-than signs.
println("a" < "b") // true
println("c" < "a") // false
In situations where we want to compare two strings regardless of how their text is capitalized, we can use the compareTo
and equals
functions with the ignoreCase
parameter set to true
. Not only does this look nicer than calling toLowerCase
on both strings separately it also has better performance:
val input = "QuICK brOWN fox"
println(input.equals("Quick Brown Fox", ignoreCase = true)) // true
Turning strings into collections
But often strings also contain multiple pieces of information which we would like to work with individually. To rip apart a string into pieces, there’s a number of options.
Most generally, the split
function is our friend here: we can specify a character, word or other string which will be used as a splitter, and the function gives us a nice list of the individual string pieces cut up by our delimiter. Consider, for example, the following input
string, which contains 5 letters, all separated by ;
:
val input = "A; B; C; D; E"
println(input.split("; ")) // A, B, C, D, E
The output is a list with five elements.
And if we only want to make a limited number of cuts, we could also pass a limit
to the split function. And then we’ll only get three pieces of text as a result, for example, with the last piece being the remainder of the string which wasn’t split any further. Let's consider the previous example once more, this time with a limit:
val input = "A; B; C; D; E"
println(input.split("; ", limit = 3)) // A, B, C; D; E
The output is a list with three elements: A
, B
, and C; D; E
.
For the special case of splitting a string up by lines, there’s the very accurately named lines
function which, as you may have guessed, gives you back your text line-by-line. The nice part here is that we don’t have to remember what the escape sequence for newlines are. Was it \n
? \r\n
? Who cares, we have the lines function!
val input = """
Well this is crazy
I'm a multiline string
So split me maybe?
""".trimIndent()
println(input.lines())
// [Well this is crazy, I'm a multiline string, So split me maybe?]
And of course, we can do all sorts of fancy things once we have a collection.
Treating strings as collections of characters
And, to be exact, even an individual string behaves like a collection – since it's a collection of characters! On the one hand, this means that we can use the array operator to pick out characters at specific indexes, which can prove quite useful. But it also means that stuff like map
ping, filter
ing, and friends all work directly on strings as well.
val input = "Hello, World"
println(input[1]) // e
println(input.filter { it.isUpperCase() }) // HW
We will tackle those collection operations in a future episode of "Kotlin Standard Library Safari" – so, if you don’t want to miss me exploring those in the future, make sure to follow @kotlin here on dev.to, and subscribe to our official YouTube channel to get updates on when a new episode is available!
Conclusion
That's all for this first segment on the Kotlin standard library. I hope that you enjoyed this brief overview of things to do with strings in Kotlin. There is, of course, still more stuff you can do. A great starting point for your own exploration of the string APIs available is the documentation on text!
If you know of some tips you'd like to see featured in a future episode, make sure to share them on Twitter and tag @sebi_io and @kotlin – and maybe we'll see your tip in a future part of this series!
I hope you learned something new, and that you will go and explore some more Kotlin!
Top comments (2)
Why is it false? 😃
Whoops! When I wrote this, I wanted to switch the operators around, but also the return value... can't have it both ways! Thanks for spotting, corrected in the article. 🎉