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.
The definition of a generic type should never be nullable. The nullability should be defined by the one using this generic. With the current Kotlin version, you can have a "String?" implementation and a "String" implementation.
You typically use a nullable value type when you need to represent the undefined value of an underlying value type. For example, a Boolean, or bool , variable can only be either true or false . However, in some applications a variable value can be undefined or missing.
The Nullable type allows you to assign a null value to a variable. Nullable types introduced in C#2.0 can only work with Value Type, not with Reference Type. The nullable types for Reference Type is introduced later in C# 8.0 in 2019 so that we can explicitly define if a reference type can or can not hold a null value.
Understand non-nullable and nullable variablesA type is only nullable if you explicitly let it hold null . As the error message says, the String data type is a non-nullable type, so you can't reassign the variable to null . To declare nullable variables in Kotlin, you need to add a ? operator to the end of the type.
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> { ... }
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With