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 print the class of
critters
. This will show up asArrayList
, notArrayList<Animal>
, because generics are only used at compile time. - We print the classes of the members of the list, which will show up as
class Frog, class Axolotl
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:
- We use
mutableListOf()
, which is a global function from Kotlin’s standard library that creates aMutableList
with some initial contents; and - We try adding a string to that list
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.