Instantiation with Generics

We have a few options for creating objects and declaring generic types.

The Formal Way

The official way for all of this is to declare the type both where we are using it (e.g., a property) and when creating the instance:

open class Animal

class Frog : Animal()

class Axolotl : Animal()

fun main() {
  val critters: ArrayList<Animal> = ArrayList<Animal>()

  critters.add(Frog())
  critters.add(Axolotl())
}

Here, we are very specifically stating that the critters property holds an ArrayList of Animal and is being initialized with an ArrayList of Animal.

If we wanted, we could skip the generic type on the initializer:

open class Animal

class Frog : Animal()

class Axolotl : Animal()

fun main() {
  val critters: ArrayList<Animal> = ArrayList()

  critters.add(Frog())
  critters.add(Axolotl())

  println(critters::class)
  println(critters.map { it::class.toString() }.joinToString())
}

Here we have two additional lines, so Klassbook has some output:

We could try to substitute a supertype, such as List, for the property type:

open class Animal

class Frog : Animal()

class Axolotl : Animal()

fun main() {
  val critters : List<Animal> = ArrayList<Animal>()

  critters.add(Frog())
  critters.add(Axolotl())
}

…except that this does not work:

error: unresolved reference: add
critters.add(Frog())
         ^
error: unresolved reference: add
critters.add(Axolotl())
         ^

As it turns out, Kotlin’s List type is immutable: you cannot add new objects to the list after initially creating it. We will explore immutable types more in an upcoming chapter.

Fortunately, Kotlin also has a MutableList interface that ArrayList happens to implement, so we can use:

open class Animal

class Frog : Animal()

class Axolotl : Animal()

fun main() {
  val critters : MutableList<Animal> = ArrayList<Animal>()

  critters.add(Frog())
  critters.add(Axolotl())
}

Implied Types

Our original example, using listOf(), did not declare a type. Nor does this slightly-modified example:

open class Animal

class Frog : Animal()

class Axolotl : Animal()

fun main() {
  val critters = mutableListOf(Frog(), Axolotl())

  critters.add("ribbit!")
}

The two changes are:

This fails with a compile error:

error: type mismatch: inferred type is String but Animal was expected
critters.add("ribbit!")
             ^

What happened is that the Kotlin compiler looked at the type of objects that we passed into mutableListOf() and determined the common supertype, then decided that critters must be a variable of that type. In this case, Frog() and Axolotl() are both Animal objects, so Kotlin will treat critters as if it were a MutableList of Animal.

The key is that Kotlin will figure out the common supertype. If we really do want to allow strings in our list, we could teach Kotlin that by adding a string at the outset:

open class Animal

class Frog : Animal()

class Axolotl : Animal()

fun main() {
  val critters = mutableListOf(Frog(), Axolotl(), "ribbit!")

  critters.add("[insert axolotl sound here]")

  println(critters::class)
  println(critters.map { it::class.toString() }.joinToString())
}

This compiles just fine, because Kotlin has decided that critters is a variable of some type that Frog, Animal, and String all have in common. As it turns out, that Kotlin type is called Any, and it serves as the base class for all Kotlin classes (akin to how Object is the base class for all Java classes).

This is not limited to some sort of magic global function:

import java.util.concurrent.atomic.AtomicReference

data class Physicist(val firstName: String)

val oppenheimer = AtomicReference(Physicist("Robert"))

Here, we use Java’s AtomicReference class, which provides a thread-safe way to access a value. We do not explicitly state that oppenheimer is an AtomicReference of Physicist — Kotlin figures that out. If we tried calling oppenheimer.set("Albert"), it would fail with:

error: type mismatch: inferred type is String but Test.Physicist! was expected
oppenheimer.set("Albert")
                ^

So, in general, you can skip the type declaration of a variable, so long as the type that Kotlin decides to use, based on your initializer, is the type that you want to use. Otherwise, declare the type explicitly yourself.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.