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.
Kotlin In Action has following to say on nullability of type parameters
Nullability of type parameters:
By default, all type parameters of functions and classes in Kotlin are nullable. Any type, including a nullable type, can be substituted for a type parameter; in this case, declarations using the type parameter as a type are allowed to be null, even though the type parameter T doesn’t end with a question mark.
How to make type parameters not-null?
To make the type parameter non-null, you need to specify a non-null upper bound for it. That will reject a nullable value as an argument.
Note that type parameters are the only exception to the rule that a question mark at the end is required to mark a type as nullable, and types without a question mark are non-null. The next section shows another special case of nullability: types that come from the Java code.