Why is a generic typed property nullable?

The default upper bound (if none specified) is Any? (Source)

In other words, when you use T, Kotlin assumes that this might be any type, be it primitive, object or a nullable reference.

To fix this add an upper type:

class Test<T: Any> { ... }

Nullable Type Parameter

Any? is the supertype of all types in Kotlin. So, when you don't specify any upper bound for the type parameter T, the default bound is Any?.

For example:

class Test<T> { }

is the same as

class Test<T : Any?> { }

This results in the T being nullable in the following example:

class Test<T> {
    private var t : T          // T can have a nullable type
}

This means that the generic type above can be instantiated with nullable as well as non-null type arguments:

val test: Test<Int> = Test()   // OK
val test: Test<Int?> = Test()  // OK

Non-null Type Parameter

Any is the supertype of all non-null types in Kotlin. So, to make a generic class accept only non-null type arguments, you need to explicitly specify Any as an upper bound of T, that is T : Any.

This results in the T being non-null in the following example:

class Test<T : Any> {
    private var t: T           // T is non-null
    private var t2: T?         // T can be used as nullable
}

The generic type with T : Any can be instantiated only with non-null type arguments and prevents the instantiation with nullable type arguments:

val test: Test<Int> = Test()   // OK
val test: Test<Int?> = Test()  // Error

lateinit var

The lateinit var must always be non-null because it is used in the cases where you want a variable to be non-null but don't want to initialize its value at the time of object creation.

So, to create the lateinit variable that has the same type as the type parameter T, the type parameter needs to be non-null too.

To achieve that, specify the upper bound T : Any explicitly:

class Test<T : Any> {
    private lateinit var t: T
}

It's worth noting that you can use a more specific type, if you have one depending on your business logic. For example, instead of T : Any, you could have T : SomeProduct, if that is what you want the upper bound to be. It just needs to be non-null.

This will ensure that the user of your class won't be able to instantiate with nullable type arguments and your assumption of the lateinit var always being non-null will hold true.


That's it! Hope that helps.

Tags:

Kotlin