2

I need to know if this line code is right, my teacher told me it's correct, but I disagree 'cause "lateinit" can't be with a variable that could be null or not. Line code:

    lateinit var text : String?

Code:

    val cadena = null
    lateinit var text : String?
    text = null
    text = cadena ?: "Hola"
    text?.let { println(text) }
Michiel Leegwater
  • 1,172
  • 4
  • 11
  • 27
  • I wonder whether there is more context to that story... It looks to me as if your teacher just had a short look at the code and assumed it could be right... the main purpose of `lateinit` is to avoid a nullable type, so I can't believe that a teacher wouldn't realize, sooner or later, that this constellation wouldn't make much sense... Did you also ask your teacher or said that you disagree and why? (in the end, we are all humans and humans make mistakes... we hope that teachers wouldn't, but... they are humans too ... most of them ;-)) – Roland Oct 21 '19 at 09:45
  • 1
    Interesting issue regarding this topic: [KT-15284 - Implement lateinit for nullable and primitive types](https://youtrack.jetbrains.com/issue/KT-15284) – Roland Oct 21 '19 at 10:02
  • @Roland Interesting reference, but it does not seem like that feature is being worked on. Besides, I think it is reasonable to test with latest stable release of the language regardless. – Enselic Oct 21 '19 at 11:09
  • @Enselic both true... I think at least for the primitive types and `lateinit` a solution is required... I wouldn't need the nullable types mentioned in that issue... on the other hand... would it really hurt to have something like `lateinit var text : String?`? it doesn't make much sense, but would it hurt? :-) – Roland Oct 21 '19 at 11:34

2 Answers2

3

You are correct and your teacher is wrong. Proof: lateinit var text : String? results in compilation error with Kotlin 1.3.50:

'lateinit' modifier is not allowed on properties of nullable types

How any teacher can possibly claim such code is correct is beyond me...

Enselic
  • 4,434
  • 2
  • 30
  • 42
  • Indeed.  It's always worth checking what the compiler says.  After all, in any disagreement between you and the compiler, the compiler is going to win :-) – gidds Oct 21 '19 at 09:47
1

I would like to add a little deep dive into lateinit properties in Kotlin.

'lateinit' modifier is not allowed on properties of nullable type - this can be found in Kotlin documentation. This kind of modifier is for special kind of constructions. It's for fields that will be initialized somewhen after object creation. For example, via DI framework or mocking framework.

But, what is under that field? If we would check it, we will simply find that before initialization property has null value. Nothing more, nothing less, just null. But, if we would like to access that property before initialization UninitializedPropertyAccessException is thrown.

In Kotlin 1.3 lateinit properties got new property - isInitialized (to use it: ::lateinitiProperty.isInitilized). So, before we access that property we are able to check if under that field is null or something else, without throwing exception.

But, lateinit means that object will be initialized later as not null property. And a programmer guarantee that this value is not null after intialization. If it could be, why just not use nullable type?

If there is a way to uninialize lateinit property? Yes, it is. Via reflection we can set that value to null again (JVM is not null-safe). And accessing that field will not finish with NPE execption, but with UninitializedPropertyAccessException. And .isInitialized will return false for field that refers to null.

And how does it work?

class MyClass {
    lateinit var lateinitObject: Any

    fun test() {
        println("Is initialized: ${::lateinitObject.isInitialized}") // false
        lateinitObject = Unit
        println("Is initialized: ${::lateinitObject.isInitialized}") // true

        resetField(this, "lateinitObject")
        println("Is initialized: ${::lateinitObject.isInitialized}") // false again

        lateinitObject // this will throw UninitializedPropertyAccessException
    }
}

fun resetField(target: Any, fieldName: String) {
    val field = target.javaClass.getDeclaredField(fieldName)

    with (field) {
        isAccessible = true
        set(target, null)
    }
}

Ofc, using lateinit that way is probably not what you want, and treat is as a curio about lateinit design in JVM.

And due to your teacher - he wasn't right. Even if lateinit may refer to null (and it does actually), you cannot declare it as a nullable type. If you need to, you don't need lateinit modifier.

Cililing
  • 4,303
  • 1
  • 17
  • 35