1. Previous: Chapter 7
  2. Next: Chapter 9
Kotlin: An Illustrated Guide • Chapter 8

Collections: Lists and Sets

Chapter cover image

So far, we’ve only worked with variables as individual values. Writing Kotlin becomes so much more interesting once we start putting variables together in a way that we can work on them as a collection. To learn about collections, let’s visit Libby, a bright young lady who’s always got a book nearby!

Who Loves to Read Books?

Libby is a voracious reader. She’s always on the lookout for a great novel, so whenever someone tells her about a good book, she jots down the title on a list that she keeps on a sheet of paper. Here are the titles that are currently on her list:

A sheet of paper with a list of books that Libby wants to read. The Kingsford Manor Mystery Tea with Agatha Mystery on First Avenue The Ravine of Sorrows Among the Aliens Books to Read

Libby has been learning to write Kotlin code in her spare time, so she also wanted to write this same list in Kotlin, and print all of the titles to the screen. Here’s what she wrote:

val book1 = "Tea with Agatha"
val book2 = "Mystery on First Avenue"
val book3 = "The Ravine of Sorrows"
val book4 = "Among the Aliens"
val book5 = "The Kingsford Manor Mystery"

println(book1)
println(book2)
println(book3)
println(book4)
println(book5)

“Hmm…”, she thought. “Every time I add a new book, I have to create a new variable. And keeping the numbers in order could be hard - if I remove book3 from the middle of the list, then book4 and book5 would need to be renamed to book3 and book4. If only there were a better way to keep track of my list of book titles…”

Introduction to Lists

Thankfully, there’s a much better way! Libby can create a collection. Let’s start with one of the most common kinds of collections in Kotlin - a list. Creating a list is easy - just call listOf() with the values that you want, separating them with commas. Let’s update Libby’s code so that it’s using a list.

val booksToRead = listOf(
    "Tea with Agatha",
    "Mystery on First Avenue",
    "The Ravine of Sorrows",
    "Among the Aliens",
    "The Kingsford Manor Mystery",
)

This code looks pretty similar to Libby’s handwritten list. In fact, let’s compare the two!

Similarities between Libby's handwritten list and the Kotlin list. The Kingsford Manor Mystery Tea with Agatha Mystery on First Avenue The Ravine of Sorrows Among the Aliens Books to Read val booksToRead = listOf ( "Tea with Agatha" , "Mystery on First Avenue" , "The Ravine of Sorrows" , "Among the Aliens" , "The Kingsford Manor Mystery" , ) Name of List Items First Last First Last

The handwritten list and the Kotlin list have a lot in common:

  1. First, they both have a name. In Kotlin, the name of the variable holding the list is kind of like the name of the list on paper.
  2. Next, both lists have items in them - in this case, the titles of the books. In Kotlin, the items in a list are called elements*.
  3. Finally, both lists have the titles in a particular order.

In the past, we’ve used the println() function to print out the contents of a variable to the screen. You can also use println() with a collection variable.

println(booksToRead)

When you do this, you’ll see its elements in order, like this:

[Tea with Agatha, Mystery on First Avenue, The Ravine of Sorrows, Among the Aliens, The Kingsford Manor Mystery]

Collections and Types

When working with collections in Kotlin, we have two different types to consider.

  1. The type of the collection we’re using.
  2. The type of the elements in the collection.

These two things together determine the overall type of the collection variable. In the case of Listing 8.2:

  1. The collection is a List.
  2. The type of the elements in the collection is String.
The kind of collection is a list, because we used listOf. The type of the elements is String, because the values are all strings. val booksToRead = listOf ( "Tea with Agatha" , "Mystery on First Avenue" , "The Ravine of Sorrows" , "Among the Aliens" , "The Kingsford Manor Mystery" , ) The kind of collection we're using is a List. Each of these elements is a String.

Once you know these two things, you can write the type of a collection variable easily. Just put the type of the collection first, then write the type of the elements second, between a left angle bracket < and a right angle bracket >. So the type of booksToRead is List<String>.

The type of a list of strings in Kotlin. List is the collection type, and String is the element type. List<String> Type of the collection Type of the elements

Let’s rewrite Listing 8.2, this time explicitly including the type information for the booksToRead collection variable.

val booksToRead: List<String> = listOf(
    "Tea with Agatha",
    "Mystery on First Avenue",
    "The Ravine of Sorrows",
    "Among the Aliens",
    "The Kingsford Manor Mystery",
)

This kind of a type is an instance of a generic. We will cover generics in detail in a future chapter. For now, just know how to write the type for a list, in case you need to use it as a function’s parameter type or return type.

Adding and Removing an Element

Libby just heard about another great book from her friend, Rebecca! She’s ready to add this new title, Beyond the Expanse, to her list. How can she do this?

Of course, she could just add one more argument to the end of listOf(). But what about adding the title after the list has already been created?

In Kotlin, once you’ve already called listOf() to create a list, that list can’t be changed. You can’t add anything to it, and you can’t remove anything from it. The fancy word for “change” in programming is mutate, so a list that doesn’t allow you to add or remove elements is called an immutable list.

Even though you can’t add or remove elements from a regular Kotlin List, you can create a new list by putting the original list together with a new element. To do this, use the plus operator. That is, use + to connect the original list with the new item, and assign it to a new variable, like this:

val booksToRead = listOf(
    "Tea with Agatha",
    "Mystery on First Avenue",
    "The Ravine of Sorrows",
    "Among the Aliens",
    "The Kingsford Manor Mystery",
)

val newBooksToRead = booksToRead + "Beyond the Expanse"

In this code, booksToRead + "Beyond the Expanse" is an expression that evaluates to a new List instance. So, by the time this code is done running, we have two collection variables - booksToRead and newBooksToRead.

This is kind of writing the new list of titles on a second sheet of paper. That way, Libby actually ends up with two lists - the original list and the new list:

Two sheets of paper - one with the original list, and one with the new list. The Kingsford Manor Mystery Tea with Agatha Mystery on First Avenue The Ravine of Sorrows Among the Aliens Books to Read The Kingsford Manor Mystery Beyond the Expanse Tea with Agatha Mystery on First Avenue The Ravine of Sorrows Among the Aliens New Books to Read

As you remember from Chapter 1, a variable can be declared with either val or var, and this includes variables that hold a collection. Keep in mind, though, that declaring a collection variable with var does not change the fact that the list itself is immutable. In other words, just declaring it with var does not make it so that you can add or remove elements. However, var does let you assign another immutable list to it.

So, by changing the booksToRead variable from val to var, the new list can be assigned to the existing variable name, like this:

var booksToRead = listOf(
    "Tea with Agatha",
    "Mystery on First Avenue",
    "The Ravine of Sorrows",
    "Among the Aliens",
    "The Kingsford Manor Mystery",
)

booksToRead = booksToRead + "Beyond the Expanse"

This is kind of like trashing the old paper list, and simply giving the new list the same name as the old one.

The original list is trashed, and the new list takes its place. The Kingsford Manor Mystery Beyond the Expanse Tea with Agatha Mystery on First Avenue The Ravine of Sorrows Among the Aliens Books to Read Tea with Agatha Books to Read

Libby’s list now has six titles in it. Just when she thought she was done updating the list, she heard from Rebecca again. “You know, I read Among the Aliens last week. It really wasn’t very good,” she said. “You shouldn’t bother reading that one.”

Libby would like to scratch that one off her list. As you can guess, you can remove an element from a list in a similar way, but instead of the plus operator, use the minus operator.

var booksToRead = listOf(
    "Tea with Agatha",
    "Mystery on First Avenue",
    "The Ravine of Sorrows",
    "Among the Aliens",
    "The Kingsford Manor Mystery",
)

booksToRead = booksToRead + "Beyond the Expanse"
booksToRead = booksToRead - "Among the Aliens"

In fact, those last two lines can be consolidated into a single line, like this:

booksToRead = booksToRead + "Beyond the Expanse" - "Among the Aliens"

Now, when Libby does println(booksToRead), she sees this on the screen:

[Tea with Agatha, Mystery on First Avenue, The Ravine of Sorrows, The Kingsford Manor Mystery, Beyond the Expanse]

“Excellent!” she thought, “My reading list is all up to date!”

List and MutableList

So far, we’ve been using a regular Kotlin List, which doesn’t allow changes to it, as we saw above. Instead, we had to create a new list by using a plus or minus operator.

However, Kotlin also provides another kind of list - one that does allow you to change it. Since these lists do allow changes, they’re called mutable lists, and they have a type of MutableList.

When using a mutable list, you can use the add() and remove() functions to add or remove elements, like this:

val booksToRead: MutableList<String> = mutableListOf(
    "Tea with Agatha",
    "Mystery on First Avenue",
    "The Ravine of Sorrows",
    "Among the Aliens",
    "The Kingsford Manor Mystery",
)

booksToRead.add("Beyond the Expanse")
booksToRead.remove("Among the Aliens")

Using a mutable list is kind of like Libby writing down her paper list with a pencil instead of a pen. She can go in and erase a title, or add another, without using another sheet of paper.

A handwritten list with one removal and one addition. The Kingsford Manor Mystery Beyond the Expanse Tea with Agatha Mystery on First Avenue The Ravine of Sorrows Among the Aliens Books to Read

Libby is ready to start reading those books! But in order know which book to start with, she needs to know how to get a single title out of the list.

Getting an Element from a List

“All right, which title is first on my list?” wondered Libby. She glanced down at her handwritten page. It was easy for her to see which one was first. “Tea with Agatha,” she noted. “Now how do I get the first title from the list in Kotlin?”

Identifying the first item in a handwritten list. The Kingsford Manor Mystery Beyond the Expanse Tea with Agatha Mystery on First Avenue The Ravine of Sorrows Books to Read First Item

As mentioned earlier, the elements of a list are in a particular order, and that order is very important for getting an individual element out of the list. Here’s how it works:

Each element in the list is given a number, called an index, based on where it is in the list. The first element has an index of 0, the second has an index of 1, the third has an index of 2, and so on.

The first element has index 0, the second has index 1, and so on. 0 1 2 3 4 val booksToRead = listOf ( "Tea with Agatha" , "Mystery on First Avenue" , "The Ravine of Sorrows" , "The Kingsford Manor Mystery" , "Beyond the Expanse" )

It’s easy to get an element out of a list once you know its index. Just call the get() member function, passing the index as the argument. For example, Libby can get the first element out of the list by calling get(0) like this:

val booksToRead = listOf(
    "Tea with Agatha",
    "Mystery on First Avenue",
    "The Ravine of Sorrows",
    "The Kingsford Manor Mystery",
    "Beyond the Expanse"
)

val firstBook = booksToRead.get(0)
println(firstBook) // Tea with Agatha

“Great!” said Libby. “Now I can easily get a single title out of the list of books!”

Rather than calling the get() function directly, you can use the indexed access operator instead, which is written with an opening bracket [ and a closing bracket ], with the index in the middle. The code in the following listing does the exact same thing as the code above.

val firstBook = booksToRead[0] 
println(firstBook) // Tea with Agatha

Kotlin developers use the indexed access operator much more than they use the get() function, so we’ll be using it from now on.

Now, getting an individual item out of the list can be helpful, but collections become especially helpful when we want to do something with each item in the list. Let’s see how to do that next!

Loops and Iterations

“Now, I’d like to print out the list of books to the screen,” said Libby to herself. “I’ll use println(booksToRead) for this!” Upon running that code, here’s what she saw:

[Tea with Agatha, Mystery on First Avenue, The Ravine of Sorrows, The Kingsford Manor Mystery, Beyond the Expanse]

“It’s nice that I can print out the list so easily, but I’d really like to see the list vertically, like my handwritten list.”

Here’s what she has in mind:

Tea with Agatha
Mystery on First Avenue
The Ravine of Sorrows
The Kingsford Manor Mystery
Beyond the Expanse

Of course, to achieve this, she could call println() on each element one by one, like this:

println(booksToRead[0])
println(booksToRead[1])
println(booksToRead[2])
println(booksToRead[3])
println(booksToRead[4])

However, writing code like this is quite tedious. Plus, it would be easy to make a mistake by printing the elements out of order, or by accidentally printing the same element more than once. In fact, this looks a whole lot like the code back in Listing 8.1!

Instead of writing out the same code for each element in the list, what if Kotlin could just go through every element, one by one, and call println() on each?

Thankfully, this is very easy to do in Kotlin! We can use the forEach() function. Here’s how it looks.

booksToRead.forEach { element -> 
    println(element)
}

When Kotlin is running this code, it runs down through println(element) for the first element, then comes back up and runs down it again for the second element, then comes back up and runs down it again for the third element, and so on. By going through this line of code over and over again, it’s as if it’s looping in circles, like this:

When using `forEach()` above, Kotlin loops over the `println(element)` statement, giving it each element in turn. "The Ravine of Sorrows" "Tea with Agatha" "The Kingsford Manor Mystery" "Beyond the Expanse" "Mystery on First Avenue" println ( element )

That’s why programming languages call this a loop - because, for each element in the collection, it cycles back through that code. It’s also generally called iterating, and each time the code is run, it’s called a single iteration.

Let’s look a little closer at forEach(), to understand why we had to structure the code the way we did.

forEach() is a member function that exists on collection variables. It’s a higher-order function that accepts a lambda. That lambda is the code that you want Kotlin to run “for each” element in the collection.

Breaking down the `forEach()` function. Lambda parameter Code to run for each element booksToRead. forEach { element -> println (element) } Collection variable

Here we named the parameter element, but you could have named it title instead. Alternatively, since this lambda has only a single parameter, you can use the implicit ‘it’ parameter instead, which makes it nice and concise. In fact, we can put it all on one line:

booksToRead.forEach { println(it) }

The result in either case is exactly what Libby wanted - the book titles are printed out vertically, just like on her paper notepad!

Tea with Agatha
Mystery on First Avenue
The Ravine of Sorrows
The Kingsford Manor Mystery
Beyond the Expanse

Collection Operations

Libby is ready to share her list of books with other people who are interested in what she’ll be reading, starting with her friend Nolan. However, when she makes a copy of the list for him, she wants to make changes to some of the titles.

“I’d really like to remove the word ‘The’ from the beginning of each title,” thought Libby. “That way, I’ll be able to sort it alphabetically, and the titles that begin with ‘The’ won’t clump together.”

Mapping Collections: Converting Elements

Sometimes when you create a new collection from an existing one, you also want to convert one or more of the elements in some way. In Libby’s case, she wants to remove the word “The” when it appears at the beginning of a title, so that they could be used for sorting.

Creating a new list, where titles that began with the word "The" no longer include that word. The Kingsford Manor Mystery Tea with Agatha Mystery on First Avenue The Ravine of Sorrows Beyond the Expanse Books to Read Kingsford Manor Mystery Beyond the Expanse Tea with Agatha Mystery on First Avenue Ravine of Sorrows Sortable Titles

Before doing that conversion on all of the titles, let’s start with just one of them. String objects have a removePrefix() function, which you can use to remove words from the beginning of the string. Here’s how you can use it:

val sortableTitle = "The Kingsford Manor Mystery".removePrefix("The ")

println(sortableTitle) // Kingsford Manor Mystery

Perfect! Now all she needs is to apply this removePrefix() function to each element in the list!

“Maybe I can use forEach(), since I know it operates on each item in the list”, thought Libby. She rolled up her sleeves, and cranked out the following code:

val sortableTitles: MutableList = mutableListOf()
 
booksToRead.forEach { title ->
    sortableTitles.add(title.removePrefix("The "))
}

sortableTitles.forEach { println(it) }

“Well, that works,” thought Libby. “But it’s a little complicated, and it’s a lot of code to write…”

The reason this is complicated is that Libby wanted to create a new collection, but forEach() doesn’t do that. It simply runs the lambda on an existing collection, and then returns Unit. What she really needs is a collection operation that runs the lambda and includes the result of that lambda as an element in a new collection.

In Kotlin, that collection operation is called map(). Here’s how Libby can use it to remove the word “The” from the beginning of titles in the new collection:

val sortableTitles = booksToRead.map { title -> 
    title.removePrefix("The ") 
}

This code does the same thing as the previous listing (except that the result is an immutable List instead of a MutableList). Like forEach(), the map() function calls the lambda once with each element in the list. However, unlike forEach(), map() will use the result of the lambda on each iteration to build out a new list.

When using `map()` above, Kotlin loops over the `title.removePrefix("The ")` expression, giving it each element in turn. "The Ravine of Sorrows" "Tea with Agatha" "The Kingsford Manor Mystery" "Beyond the Expanse" "Mystery on First Avenue" title . removePref ix ( "The " ) "Ravine of Sorrows" "Tea with Agatha" "Kingsford Manor Mystery" "Beyond the Expanse" "Mystery on First Avenue"

When you print each element of the list, you can see that both The Ravine of Sorrows and The Kingsford Manor Mystery have been updated so that the word “The” is not at the beginning.

Tea with Agatha
Mystery on First Avenue
Ravine of Sorrows
Kingsford Manor Mystery
Beyond the Expanse

Let’s take a closer look at the map() function:

Breakdown of the `map()` function. Lambda parameter Evaluates to a value that becomes an element in the new list. val sortableTitles = booksToRead. map { title -> title. removePrefix ( "The " ) } Original list variable New list variable
  • Similar to forEach(), the map() function is a higher-order function that takes a lambda.
  • That lambda will run once for each element in the list.
  • The result of the lambda will be an element in the new collection.
  • The map() function returns that new collection.

Functions like forEach() and map() are called collection operations, because they’re functions that perform some operation on (that is, they do something with) a collection.

“Perfect!” said Libby, “Now that the titles have been changed like I want, maybe I can sort them?”

Sorting Collections

The forEach() and map() functions are only two of many collection operations in Kotlin. Another one that can be quite helpful is called sorted().

Since the map() function returns a collection, Libby can just call sorted() immediately after the call to map(), like this:

val sortedTitles = booksToRead.map { title -> title.removePrefix("The ") }.sorted()

When she printed out the elements of sortedTitles, she saw the output she was hoping for!

Beyond the Expanse
Kingsford Manor Mystery
Mystery on First Avenue
Ravine of Sorrows
Tea with Agatha

In order to make things easier to read, each collection operation can go on its own line, like this:

val sortedTitles = booksToRead
    .map { title -> title.removePrefix("The ") }
    .sorted()

This code is identical to the previous listing except for the formatting. In other words, all of the letters and punctuation are exactly the same and in the same order - it’s only the space between them that’s different.

Writing the collection operations vertically like this can be helpful because it makes it easy to scan down the lines to see what collection operations are involved and what order they’re in. For example, first the titles are mapped, and then the titles are sorted. For that reason, Kotlin developers often format their code this way.

Filtering Collections: Including and Omitting Elements

Libby is excited! Now she’s got a list of books - sorted alphabetically - to share with Nolan.

“I can’t wait to see your list of books,” said Nolan. “Just remember - I only read mystery novels!”

“Only mysteries…?” repeated Libby. “Okay,” she thought to herself, “the final thing I need to do is remove the titles that are not mysteries.” She pulled out a sheet from her notepad, and wrote a customized list just for Nolan, omitting any title that is not a mystery.

Creating a new list that only includes certain titles from the original list. Kingsford Manor Mystery Tea with Agatha Mystery on First Avenue Ravine of Sorrows Beyond the Expanse Books to Read Kingsford Manor Mystery Mystery on First Avenue Books for Nolan

“How can I do this in Kotlin?” she wondered.

As you probably guessed, Kotlin includes a collection operation that makes this easy, and unsurprisingly, it’s called filter().

Just like an air filter that blocks unwanted dust and allergens from getting through to your air conditioner system, a Kotlin list filter blocks elements that you don’t want to get through to a new list!

Let’s use the filter() function to filter down the list of books to just those that have “Mystery” in the title:

val booksForNolan = booksToRead
    .map { title -> title.removePrefix("The ") }
    .sorted()
    .filter { title -> title.contains("Mystery") }

The filter() function is similar to the map() function above - it takes a lambda as an argument, and that lambda will be invoked once for each title in the original list. Unlike the map() function, however, the lambda for filter() must return a Boolean. If it returns true for an element, then that element is passed into the new collection (i.e., booksForNolan in this case). If it returns false, then it’s omitted from the new collection.

When using `filter()` above, Kotlin loops over the `title.contains("Mystery")` expression, giving it each element in turn. "The Ravine of Sorrows" "Tea with Agatha" "The Kingsford Manor Mystery" "Beyond the Expanse" "Mystery on First Avenue" title . contains ( "Mystery" ) false false true false true

Here’s a breakdown of how to use the filter() function:

Breakdown of the `filter()` function. Lambda parameter Determines whether this element is included in the new list. val booksForNolan = booksToRead. filter { title -> title. contains ( "Mystery" ) } Original list variable New list variable

Printing each element of the list, here’s what Libby saw:

Kingsford Manor Mystery
Mystery on First Avenue

“Great,” she said. “The list is exactly like I wanted it. It includes only mysteries, and it’s sorted properly!”

Collection Operation Chains

Let’s look at that code again:

val booksForNolan = booksToRead
    .map { title -> title.removePrefix("The ") }
    .sorted()
    .filter { title -> title.contains("Mystery") }

In Kotlin, it’s common to put multiple collection operations together like this, one after another. When we do this, it’s called chaining the collection operations - each operation is like one link in the chain. In this code listing, the map(), sorted(), and filter() calls are chained together.

A collection operation chain. Each operation is like one link in the chain. val booksForNolan = booksToRead . map { title -> title. removePrefix ( "The " ) } . sorted () . filter { title -> title. contains ( "Mystery" ) } Collection operation chain

Keep in mind that the operation chain is not mutating a single list. In fact, each of these operations creates a new list. The list that’s created by the final operation, filter(), is the list that is assigned to the variable booksForNolan. The intermediate lists - that is, the lists that are created by the collection operations inside the chain - are used by the next operation in the chain, but are not assigned to any variable. It’s still important to keep these intermediate lists in mind, though. This next illustration shows the list that’s involved at each step in the chain.

A new list is created at each step of the operation chain. Kingsford Manor Mystery Mystery on First Avenue Beyond the Expanse Kingsford Manor Mystery Mystery on First Avenue Ravine of Sorrows Tea with Agatha Tea with Agatha Mystery on First Avenue Ravine of Sorrows Kingsford Manor Mystery Beyond the Expanse Tea with Agatha Mystery on First Avenue The Ravine of Sorrows The Kingsford Manor Mystery Beyond the Expanse val booksForNolan = booksToRead . map { title -> title. removePrefix ( "The " ) } . sorted () . filter { title -> title. contains ( "Mystery" ) }

Whenever you’ve got a collection operation chain like this, it’s helpful to consider how many elements are in each intermediate list. For example, the code in Listing 8.21 has the filter() call at the end of the chain. But what if it went at the beginning of the chain instead, like this?

val booksForNolan = booksToRead
    .filter { title -> title.contains("Mystery") }
    .map { title -> title.removePrefix("The ") }
    .sorted()

By doing this, the intermediate list that filter() produces would only have two elements, in which case the map() function would only need to invoke its lambda twice instead of five times, and sorted() would only have two items to sort instead of five. In this example, the final list is the same either way, but Listing 8.22 is likely to be more efficient than Listing 8.21.

Here’s an illustration showing the list involved at each step when placing the filter() call at the top. Notice that the intermediate lists have fewer elements than they did in the previous illustration.

Fewer elements are processed in `map()` and `sorted()` when `filter()` runs first. Kingsford Manor Mystery Mystery on First Avenue Mystery on First Avenue Kingsford Manor Mystery Mystery on First Avenue The Kingsford Manor Mystery Tea with Agatha Mystery on First Avenue The Ravine of Sorrows The Kingsford Manor Mystery Beyond the Expanse val booksForNolan = booksToRead . filter { title -> title. contains ( "Mystery" ) } . map { title -> title. removePrefix ( "The " ) } . sorted ()

On a small list like this, it’s not a big deal, but on a list that has hundreds or thousands of elements, you could see how this could improve the performance of your code - that is, it would run much faster!

Other collection operations

Kotlin has many other collection operations that are easy to use! Just to give you an idea, here are a few others that might be helpful to you.

  • drop(3) - The new list omits the first 3 elements from the original list.
  • take(5) - The new list uses only the first 5 elements from the original list.
  • distinct() - The new list will omit duplicate elements, so that each element is included only once.
  • reversed() - The new list will have the same elements as the original, but their order will be backwards.

You can see a more complete list of them in the Kotlin API documentation.

Introduction to Sets

Before we wrap up this chapter, it’s worth noting that lists aren’t the only kind of collection in Kotlin. Lists are probably the most frequently used, but another helpful collection type is called a set. Whereas lists are helpful for ensuring that its elements are in a particular order, sets are helpful for ensuring that each element in it is always unique.

For example, Nolan’s favorite mystery author, Slim Chancery, has written three books, and Nolan is proud to say he’s got the whole set.

Creating a set in Kotlin is just as easy as creating a list. Simply use setOf() or mutableSetOf() instead of listOf() or mutableListOf().

val booksBySlim: Set = setOf(
    "The Malt Shop Caper",
    "Who is Mrs. W?",
    "At Midnight or Later",
)

When you add an element to a set that already has that value, the set will remain unchanged.

val booksBySlim: MutableSet = mutableSetOf(
    "The Malt Shop Caper",
    "Who is Mrs. W?",
    "At Midnight or Later",
)

booksBySlim.add("The Malt Shop Caper")

println(booksBySlim)
// [The Malt Shop Caper, Who is Mrs. W?, At Midnight or Later]

Note that a set does not guarantee the order of its elements when you print them out or use a collection operation on it. It’s possible that the elements will be in the same order that you added them, but don’t depend on it!

Because sets don’t have any particular order to their elements, their elements do not have indexes. For that reason, sets do not even include a get() function!

The key takeaway is that:

  1. Lists have elements in a guaranteed order, and can contain duplicates.
  2. Sets have elements in no particular order, and are guaranteed not to contain duplicates.

Also, you can convert a list into a set, or the other way around. Simply use toSet() or toList(). Just remember that if you convert a list to a set, you’ll lose duplicate elements, and the order could possibly be different!

val bookList = listOf(
    "The Malt Shop Caper",
    "At Midnight or Later",
    "The Malt Shop Caper",
)

val bookSet = bookList.toSet()         // bookSet has two elements
val anotherBookList = bookSet.toList() // anotherBookList also has two elements

Summary

Enjoying this book?
Pick up the Leanpub edition today!

Kotlin: An Illustrated Guide is now available on Leanpub See the book on Leanpub

Up until this chapter, we’ve only worked with individual variables. By using collections like lists and sets, we’re able to do things with entire groups of values, which opens a whole new world of possibilities! In this chapter, you learned about collections, including:

We discovered how easy it is to get an element from a list by its index. However, sometimes you want an easy way to get an element by some other information about it. For example, instead of getting a book by its positional index, you might want to get a book by its ISBN (that long number above the barcode on the back). In the next chapter, we’ll learn about another way to group elements that will make it easy to do this! See you then!

Share this article:

  • Share on Twitter
  • Share on Facebook
  • Share on Reddit