How do I create and use lists, sets, and maps in Kotlin?

In Kotlin, the main collection types are List, Set, and Map.

Kotlin provides both read-only and mutable versions:

Collection Read-only Mutable
List List<T> MutableList<T>
Set Set<T> MutableSet<T>
Map Map<K, V> MutableMap<K, V>

Lists

A list is an ordered collection. It can contain duplicate elements.

Read-only list

val numbers = listOf(1, 2, 3, 3)

println(numbers[0])        // 1
println(numbers.size)      // 4
println(numbers.contains(2)) // true

You cannot add or remove items from a read-only List.

val names = listOf("Alice", "Bob", "Charlie")

for (name in names) {
    println(name)
}

Mutable list

val names = mutableListOf("Alice", "Bob")

names.add("Charlie")
names.remove("Alice")
names[0] = "Bobby"

println(names) // [Bobby, Charlie]

You can also create an empty mutable list:

val items = mutableListOf<String>()

items.add("Book")
items.add("Pen")

println(items) // [Book, Pen]

Sets

A set is a collection of unique elements. It does not allow duplicates.

Read-only set

val numbers = setOf(1, 2, 3, 3)

println(numbers) // [1, 2, 3]
println(2 in numbers) // true

Mutable set

val fruits = mutableSetOf("Apple", "Banana")

fruits.add("Orange")
fruits.add("Apple") // Duplicate, ignored
fruits.remove("Banana")

println(fruits) // [Apple, Orange]

Empty mutable set:

val ids = mutableSetOf<Int>()

ids.add(101)
ids.add(102)

println(ids) // [101, 102]

Maps

A map stores key-value pairs. Each key is unique.

Read-only map

val ages = mapOf(
    "Alice" to 25,
    "Bob" to 30,
    "Charlie" to 35
)

println(ages["Alice"]) // 25
println(ages["Unknown"]) // null
println(ages.containsKey("Bob")) // true
println(ages.containsValue(30)) // true

Mutable map

val scores = mutableMapOf(
    "Alice" to 90,
    "Bob" to 85
)

scores["Charlie"] = 95
scores["Alice"] = 100
scores.remove("Bob")

println(scores) // {Alice=100, Charlie=95}

Empty mutable map:

val phoneBook = mutableMapOf<String, String>()

phoneBook["Alice"] = "123-456"
phoneBook["Bob"] = "987-654"

println(phoneBook["Alice"]) // 123-456

Common operations

Iterating over a list or set

val colors = listOf("Red", "Green", "Blue")

for (color in colors) {
    println(color)
}

Iterating over a map

val ages = mapOf(
    "Alice" to 25,
    "Bob" to 30
)

for ((name, age) in ages) {
    println("$name is $age years old")
}

Filtering

val numbers = listOf(1, 2, 3, 4, 5, 6)

val evenNumbers = numbers.filter { it % 2 == 0 }

println(evenNumbers) // [2, 4, 6]

Mapping values

val names = listOf("alice", "bob", "charlie")

val uppercaseNames = names.map { it.uppercase() }

println(uppercaseNames) // [ALICE, BOB, CHARLIE]

Sorting

val numbers = listOf(5, 2, 8, 1)

val sorted = numbers.sorted()

println(sorted) // [1, 2, 5, 8]

Checking contents

val names = listOf("Alice", "Bob")

println("Alice" in names) // true
println("Charlie" !in names) // true

Choosing between them

Use a List when:

  • Order matters
  • Duplicates are allowed
  • You access elements by index
val tasks = listOf("Write", "Test", "Deploy")

Use a Set when:

  • Values must be unique
  • You mainly check whether something exists
val uniqueTags = setOf("kotlin", "backend", "api")

Use a Map when:

  • You need key-value lookup
  • Each key maps to one value
val userRoles = mapOf(
    1 to "Admin",
    2 to "Editor",
    3 to "Viewer"
)

Quick summary

val readOnlyList = listOf("A", "B", "C")
val mutableList = mutableListOf("A", "B")
mutableList.add("C")

val readOnlySet = setOf("A", "B", "A") // [A, B]
val mutableSet = mutableSetOf("A", "B")
mutableSet.add("C")

val readOnlyMap = mapOf("Alice" to 25, "Bob" to 30)
val mutableMap = mutableMapOf("Alice" to 25)
mutableMap["Bob"] = 30

In short:

  • listOf() creates a read-only list
  • mutableListOf() creates a mutable list
  • setOf() creates a read-only set
  • mutableSetOf() creates a mutable set
  • mapOf() creates a read-only map
  • mutableMapOf() creates a mutable map

How do I use List.of, Set.of, and Map.of factory methods?

The List.of, Set.of, and Map.of factory methods were introduced in Java 9 to create immutable collections in a simpler and more concise way. These methods directly create unmodifiable instances of List, Set, or Map.

Usage of List.of

The List.of method is used to create an unmodifiable List containing the provided elements. The key characteristics are:

  • The list is immutable, meaning you cannot modify its contents (e.g., add, remove, replace elements).
  • If you attempt to modify the list, a java.lang.UnsupportedOperationException will be thrown.

Key Points:

  1. It is null-safety aware, meaning null is not allowed as an element.
  2. The created list maintains the insertion order.

Example

List<String> names = List.of("Rosa", "John", "Mary", "Alice");
System.out.println(names); // Output: [Rosa, John, Mary, Alice]

names.add("Bob"); // Throws java.lang.UnsupportedOperationException

Note: Attempting to pass a null element will result in NullPointerException.


Usage of Set.of

The Set.of method creates an immutable Set containing the given elements. The key characteristics are:

  • The set is unmodifiable, so you cannot add or remove elements after creation.
  • It does not allow duplicate elements.
  • It does not allow null elements.

Key Points:

  1. A java.lang.IllegalArgumentException is thrown if duplicate elements are provided during creation.
  2. Since a Set does not guarantee order, the order of elements in the resulting set is not predictable.

Example

Set<String> items = Set.of("apple", "banana", "orange");
System.out.println(items); // Output: [apple, banana, orange] (order may vary)

items.add("kiwi"); // Throws java.lang.UnsupportedOperationException

Note: If duplicate elements are passed, an exception will be thrown:

Set.of("apple", "banana", "apple"); // Throws IllegalArgumentException

Usage of Map.of

The Map.of method creates an immutable Map with key-value pairs. The characteristics are:

  • The created map is unmodifiable.
  • Both null keys and null values are not allowed.
  • Duplicate keys are not allowed, and attempting to use duplicate keys will throw IllegalArgumentException.

Example

Map<String, Integer> map = Map.of("John", 25, "Mary", 30, "Alice", 27, "Rosa", 22);

System.out.println(map); 
// Output (order may vary): {John=25, Mary=30, Alice=27, Rosa=22}

map.put("Bob", 31); // Throws java.lang.UnsupportedOperationException

Note: Avoid duplicate keys. For example:

Map.of("Key1", 1, "Key2", 2, "Key1", 3); // Throws IllegalArgumentException

Advantages of List.of, Set.of, and Map.of

  1. Immutable collections help ensure thread safety without additional synchronization.
  2. Improved code conciseness compared to using Collections.unmodifiableList, Collections.unmodifiableSet, or Collections.unmodifiableMap.
  3. Simplified initialization with a clean and readable API.

For larger maps (more than 10 entries), use Map.ofEntries for better clarity:

Map<String, Integer> largeMap = Map.ofEntries(
    Map.entry("Alice", 27),
    Map.entry("John", 25),
    Map.entry("Mary", 30),
    Map.entry("Rosa", 22)
);

How do I use Set.of() factory method to create a set object?

As with List.of(), in Java 9, the Set.of() factory method can be used to create an unmodifiable set of specified elements.

Here is a simple example:

package org.kodejava.util;

import java.util.Set;

public class SetOfExample {
    public static void main(String[] args) {
        Set<String> names = Set.of("Rosa", "John", "Mary", "Alice");

        for (String name : names) {
            System.out.println(name);
        }
    }
}

Output:

John
Rosa
Alice
Mary

In this example, the Set.of("Rosa", "John", "Mary", "Alice") statement creates an unmodifiable set of strings containing “Rosa”, “John”, “Mary”, and “Alice”. The resulting set is unmodifiable, so attempting to add, update, or remove elements from it will throw an UnsupportedOperationException.

If you try to create a Set by providing a duplicate elements, an IllegalArgumentException will be thrown. A Set is a type of collection container that cannot have duplicate values in it.

Note that the Set.of() method doesn’t accept null values. If you try to insert a null value, it will throw a NullPointerException. If you add a null value using the add() method UnsupportedOperationException will be thrown.

Set.of() is overloaded similarly to List.of(), allowing you to create a set with varying numbers of elements. The below examples demonstrate the use of Set.of() with different numbers of arguments:

Set<String> a = Set.of(); // An empty set
Set<String> b = Set.of("One"); // A set with one element
Set<String> c = Set.of("One", "Two"); // A set with two elements
// ...
Set<String> j = Set.of("One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"); // A set with ten elements

If you need to create a set with more than 10 elements, Set.of() offers an overloaded version that accepts an array or varargs:

Set<String> set = Set.of("One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven");

Remember that sets created with Set.of() are unmodifiable. Attempting to add, remove or change an element in these sets after their creation causes an UnsupportedOperationException.

Also, Set.of() doesn’t allow duplicate or null elements. If you pass duplicate or null values, it will throw IllegalArgumentException and NullPointerException respectively.

How do I use Collectors.toSet() method?

The Collectors.toSet() method is a method from the java.util.stream.Collectors class that provides a Collector able to transform the elements of a stream into a Set.

Here’s an example of using the Collectors.toSet() method:

package org.kodejava.stream;

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class CollectorsToSet {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("Apple", "Banana", "Orange", "Apple", "Banana", "Cherry");

        Set<String> uniqueFruits = fruits.stream()
                .collect(Collectors.toSet());

        System.out.println(uniqueFruits);
    }
}

Output:

[Apple, Cherry, Orange, Banana]

In this example, we have a List<String> of fruits, which contains some duplicate elements. When we stream the list and collect it into a Set using Collectors.toSet(), the result is a Set<String> that only includes the unique fruit names, as a Set doesn’t allow duplicate values.

Remember, like collect other terminal operation, it triggers the processing of the data and will return a collection or other specificity defined type. In case of Collectors.toSet(), the result is a Set.

How do I create a generic Set?

In another post of Java Generics you have seen how to create a generic collection using List in this example you will learn how to make a generic Set. Making a Set generic means that we want it to only hold objects of the defined type. We can declare and instantiate a generic Set like the following code.

Set<String> colors = new HashSet<>();

We use the angle brackets to define the type. We need also to define the type in the instantiation part, but using the diamond operator remove the duplicate and Java will infer the type itself. This declaration and instantiation will give you a Set object that holds a reference to String objects. If you tried to ask it to hold other type of object such as Date or Integer you will get a compiled time error.

Set<String> colors = new HashSet<>();
colors.add("Red");
colors.add("Green");
colors.add("Blue");

//colors.add(new Date()); // Compile time error!

The code for adding items to a set is look the same with the old way we code. But again, one thing you will get here for free is that the compiler will check to see if you add the correct type to the Set. If we remove the remark on the last line from the snippet above we will get a compiled time error. Because we are trying to store a Date into a Set of type String.

Now, let see how we can iterate the contents of the Set. First we’ll do it using the Iterator. And here is the code snippet.

Iterator iterator = colors.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

When using a generic Set it will know that the iterator.next() will return a type of String so that you don’t have use the cast operator. Which of course will make you code looks cleaner and more readable. We can also using the for-each loop when iterating the Set as can be seen in the following example.

for (String color : colors) {
    System.out.println(color);
}