1

please help me understand how to properly initialize "pets"

class Person:

class Person(
        val height: Int,
        val weight: Int,
        val name: String,
        ) {
    lateinit var pets: HashSet<Animal>

    fun buyPet():Unit{
        this.pets.add(Animal((0..100).random(), (0..100).random(), getRandomString((3..12).random())))
    }

    private fun getRandomString(length: Int) : String {
        val allowedChars = ('A'..'Z') + ('a'..'z')
        return (1..length)
                .map { allowedChars.random() }
                .joinToString("")
    }

}

class Animal:

data class Animal(
        val energy:Int,
        val weight:Int,
        val name:String) {
}

main:

fun main() {
    val person1=Person(187, 85, "Denis")
    person1.buyPet()
    println(person1.pets)
}

I am getting this error

Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property pets has not been initialized at classes_06.Person.buyPet(Person.kt:35)

ispite
  • 77
  • 1
  • 10

2 Answers2

2

Replace

lateinit var pets: HashSet<Animal>

With

val pets = mutableSetOf<Animal>()

In the original code pets is declared, but never initialised, and lateinit is just telling the compiler "do not complain about this, I'll initialise it later". When doing Kotlin, try to avoid using lateinit as much as possible.

This other SO question might give you a bit more info in general terms about what is declaring and initialising a variable.

Augusto
  • 28,839
  • 5
  • 58
  • 88
  • 1
    “ When doing Kotlin, try to avoid using lateinit as much as possible” - such general declaration without reasoning is why downvote exists. No official Kotlin docs urge that you avoid the `lateinit`, this statement is completely cooked up right here. – Abhijit Sarkar Feb 27 '21 at 21:31
  • 2
    @AbhijitSarkar It's sensible advice, though.  If you use `lateinit`, the compiler can't make its usual checks that the field gets initialised, risking runtime errors such as in this question.  There are some specific circumstances where `lateinit` is needed (such as fields set by frameworks such as Spring); but outside those, there's little need for it, and your code will be safer if you can avoid it. – gidds Feb 27 '21 at 22:23
  • @gidds `lateinit` is just a NPE alternative. You can replace `lateinit` with a nullable variable in the code. There's no way to completely avoid null checks in code (directly or indirectly), hence there's no reason to single out `lateinit` either. – Abhijit Sarkar Feb 28 '21 at 00:05
  • 1
    @AbhijitSarkar `lateinit` is intended for cases where your class isn't ready to use at construction time (Android, Spring), but you can assign the variable at the first entry point of your class. You only have to remember one location where you need to initialize the variable, and then null-checks won't be needed anywhere else. If you have to do `isInitialized` checks everywhere you use it, a nullable variable is more sensible because (1) the compiler will enforce the check and (2) you don't have to resort to reflection each time you check it. – Tenfour04 Feb 28 '21 at 15:08
  • Hi @AbhijitSarkar the official Kotlin docs also don't mention how to design software and clean code practices neither. Just 18 days ago an 'idioms' section was added to the docs (just to point that the whole Kotlin is WiP). The explanation why is not good, is clear in my response, it skips a compiler check. Please feel free to join the Kotlin slack team and ask there what other people think - https://kotlinlang.org/community/ – Augusto Feb 28 '21 at 21:50
2

To complement Agusto’s answer:

Lateinit works in cases on which you can’t define the value of the variable immediately on the object construction, for any reason. Therefore, it expects you to define the value of the lateinit variable at some “late-r” point before you try to do anything else with it.

In your case it seems perfectly fine to define the variable “pets” in construction. So you can remove the lateinit modifier and define it right away as Agusto pointed out.

Or if need lateinit for any reason, you could check if it has been initialized before, and if not, define it in the buyPet() function before performing an “add” to it:

    fun buyPet(): Unit{
        if(!::pets.isInitialized) {
           pets = mutableSetOf<Animal>()
        }
   
        this.pets.add(Animal((0..100).random(), (0..100).random(), getRandomString((3..12).random())))
    }

See this question for further details about the isInitialized property

serivesmejia
  • 23
  • 2
  • 5