Get up to 80 % extra points for free! More info:

Lesson 1 - Introduction to collections and genericity in Kotlin

In today's lesson of our Kotlin course, we'll introduce collections and genericity and go over a bit of background information about it. Then, we'll discuss different build-in collections in more detail in the following lessons.

Collections

The term "collection" refers to a set of items which are mostly of the same data type, and are used for a specific purpose. Throughout the Kotlin course, we've already encountered two types of collections. Arrays and a briefly mentioned List. Although they often look similar from the outside, they work in very different ways, internally, and we choose them according to our intents and purposes.

Generic and non-generic collections

If we thought about making our own collection, we'd run into issues very soon. One of which would be choosing the data type for the collection items. For example, if we wanted to program our own List, we would create a MyList.kt class and add the appropriate methods to it. Since we want to make our collection universal and to be able to store any sort of items, e.g. both Ints or users, we would run into issues with the data types of the elements within the collection. This problem can be solved by generic collections which are different from the non-generic ones.

Non-generic collections

Since we know that all data types are descendants of the Any class, we could use this data type to store elements in our collection. We would then be able to put anything into our collection. The downside to this approach is that the collection won't know the actual type for each of the items. Therefore, it would only be able to return these elements simply as general objects. Meaning that we would have to cast anything returned by the collection.

Let's have a look at what would such a non-generic collection look like in Kotlin if we tried to make things more complicated. We'll use the Array<Any> type:

val array = arrayOf("item", 123, 30.0)

val item: String = array[0] as String

We create an Array of various item types (String, Int, Double), resulting in an Array<Any> being created. In order to retrieve an item back, e.g. as a String, we need to re-cast it first.

Generic collections

Generic collections solve the data-type problem for the Kotlin language. They introduce genericity. Simply speaking, it's the ability to specify the data type at the moment when an instance is created. In the collection class, we work with a generic type which serves as a placeholder for future data types. You may see it as a class that changes into others when an instance is created, for example, a String. It's sort of a class parametrization.

We already know the generic Array and that the data type (parameter) of generic classes uses angle brackets. We only have the ability to specify the data type once, when we're creating the collection. This way, we no longer need to cast items. When reading, we always get an object of the given type:

var array: Array <String> = arrayOf("item", "item_2", "item_3")

val item: String = array[0]

Generic collections have replaced non-generic collections which are not used often nowadays. In Kotlin, as in a purely modern language, there are actually no non-generic collections, unless you force the Any type via genericity.

Genericity

Genericity is, of course, a feature of the Kotlin language, so we have the privilege to use it in our classes.

At this point, we won't bother with creating our own collection. Instead, let's create a simple class that will manage a single variable. The variable will be generic, so we'll be able to store any data type in it. Create a new console application project and name it Genericity. Add a new class and name it MyClass. Let's add the generic parameter in its declaration and name it T:

class MyClass<T> {
}

We are able to enter multiple generic parameters into the angle brackets, separated by commas. This may be useful at times, we'll go further into things like this once we get to generic dictionaries.

Let's move to the main() function and create an instance of our class:

val t = MyClass<Int>()

Don't forget to provide the angle brackets in both the data type and the constructor. We've specified the int data type in the T parameter for this class instance. We could also make another instance of the same class and give it a totally different data type, e.g. String. For our intents and purposes, a single class is enough for storing multiple data types.

Let's continue and create a class property. We are able to use T as an ordinary data type:

class MyClass<T>(private val variable: T) {
}

Make sure to update the instance creation in the main() function:

val t = MyClass<Int>(10)

Kotlin is smart enough, so we can leave the type declaration out and it will determine the type based on the parameter:

val t = MyClass(10) // Of the MyClass<Int> type

The instance contains the variable property now, which is of the Int type and contains a value of 10.

We could even add a method with an extra generic parameter (other than the one that the class currently has). It could look like this:

fun<T2> compare(a: T2): Boolean = (a == variable)

Let's compare our Int with another data type:

t.compare<String>("15")

Or we can call:

t.compare("15")

Kotlin provides even more powerful constructs such as the out and in keywords when declaring generics, but it's too complicated to include it in this introduction. It's more of a topic for advanced audience and people who know Java.

Other constructs

For completeness' sake, we'll introduce you to a few more constructs.

A generic class parameter can be specified in more detail, furthermore, it can be limited using the colon after the generic parameter. We have to make sure that the data type implements the Comparable<*> interface (I'll explain the asterisk below):

class MyClass<T: Comparable<*>>(private val variable: T) {
    // ...
}

This allows us to call the interface's methods on the variables of the T type from within the class. The interface itself can also contain a generic parameter (as in this case), so we could use generic types in its methods' headers.

Here, the * works as a kind of a wild card, we want any class that implements the Comparable interface but we don't care about the type it's compared with. If we wanted a class that can only be compared with Ints, it'd be as follows:

class MyClass<T: Comparable<Int>>(private val variable) {
    // ...
}

To top it all off, let's go over how to limit the parameter type in terms of inheritance.

class Trida<A: B, B: C, C> {
}

In the example above, we declare a class with three generic parameters where A is a descendant of B and B is a descendant of C.

In the next lesson, Lists with arrays in Kotlin, we'll look at lists.


 

All articles in this section
Collections in Kotlin
Skip article
(not recommended)
Lists with arrays in Kotlin
Article has been written for you by Samuel Kodytek
Avatar
User rating:
No one has rated this quite yet, be the first one!
I'm intereseted in JVM languages and Swift
Activities