Why is a generic typed property nullable?

I am trying to create a parameterized class with a lateinit non-nullable property of the generic type:

class Test<T> {


private lateinit var t : T


private lateinit var s : String


}

The latter is allowed, but the former is not. The compiler returns the following error:

Error:(7, 11) ''lateinit'' modifier is not allowed on nullable properties

Since I didn't declare T?, I am confused as to why this is the case.

16093 次浏览

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> { ... }

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.

T, when used as a type parameter, is always null. (All type parameters are nullable). You don't need to declare T?, only T is needed. To use declare an upper bound, do this public class Foo<T: Any> (Any isn't nullable)

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.