Reputation: 100468
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.
Upvotes: 86
Views: 22532
Reputation: 340
When adding Platform Types in the mix, it even gets more fun
data class Dto(
val ids: List<UUID>
)
that is why you can do
val ids = arrayListOf(UUID.randomUUID()) // ArrayList<UUID!> inferred
ids.add(null)
val body = Dto(ids = ids)
but not
val ids : ArrayList<UUID?> = arrayListOf(UUID.randomUUID())
ids.add(null)
val body = Dto(ids = ids)
public fun <T> arrayListOf(vararg elements: T): kotlin.collections.ArrayList<T> /* = java.util.ArrayList<T> */
typealias ArrayList<E> = java.util.ArrayList<E>
it's just a typealias for java type ArrayList<E>
UUID!
indicated that it is a platform type
or? https://pl.kotl.in/U-5aHGBWK
Upvotes: 0
Reputation: 48399
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
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.
Upvotes: 12
Reputation: 33839
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> { ... }
Upvotes: 138
Reputation: 8355
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.
Upvotes: 3
Reputation: 294
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)
Upvotes: 0