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 varThe 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