2

I have the code below

class Sample {

   var variable1: SomeClass? = null
   var variable2: SomeClass? = null
   var variable3: SomeClass? = null


   fun checkVariable() {
      when {
         variable1 != null -> variable1!!.doSomething()
         variable2 != null -> variable2!!.doSomething()
         variable3 != null -> variable3!!.doSomething()
      }
   }

}

I'm hoping I can make variableX after the -> non-nullable, so I don't need to !!. I can avoid !! with

variable1 !== null -> variable1?.doSomething()

But is there away to do this more elegantly that I can have a non-nullable variable to access the doSomething()?

Elye
  • 53,639
  • 54
  • 212
  • 474

3 Answers3

3

The error is not because of the when. You just cannot smart cast class-level vars.

In this case, since all your variables are of the same type and you are doing the same thing on all of them, your code can be simplified to:

(variable1 ?: variable2 ?: variable3)?.doSomething()

In other words, you are finding the first non-null out of the three variables, and calling doSomething on it.

If your variables are not of the same type, and you are doing different things to each of them, you can do:

variable1?.also {
    it.doSomething()
} ?: variable2?.also {
    it.doSomethingElse()
} ?: variable3?.also {
    it.doAnotherThing()
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
1

Not sure if it's more elegant but you could maybe write this instead:

fun getStrLength() = (variable1 ?: variable2 ?: variable3)?.doSomething()


Another way to remove the !! is to first store them in local variables like

fun getStrLengths() {

    val variable1 = variable1
    val variable2 = variable2

    val variable3 = variable3
 
    when {

        variable1 != null -> variable1.doSomething()

        variable2 != null -> variable2.doSomething()

        variable3 != null -> variable3.doSomething()

    }
}
Ivo
  • 18,659
  • 2
  • 23
  • 35
0

Lazy initialisation

You could make the properties non-nullable with lateinit var.

This would prevent the need for any null checks.

You can check to see if the properties are present with isInitialized instead of a non-null check.

class Sample {
  lateinit var variable1: SomeClass
  lateinit var variable2: SomeClass
  lateinit var variable3: SomeClass

  fun checkVariable() {
    when {
      // so long as a value is initialised, there is no need for null checks
      ::variable1.isInitialized -> variable1.printName()
      ::variable2.isInitialized -> variable2.printName()
      ::variable3.isInitialized -> variable3.printName()
    }
  }
}

class SomeClass(val name: String) {
  fun printName() {
    println(name)
  }
}

Unsetting values

This can be useful to avoid null checks, but it would prevent 'unsetting' previously set variables with null.

val sample = Sample()

sample.variable1 = SomeClass("foo")

sample.variable1 = null // ERROR: Null can not be a value of a non-null type SomeClass

Whether unsetting values is required or not depends on your use-case.

Example

fun main() {

  val sample = Sample()

  println("first check:")
  sample.checkVariable()
  println("---")

  sample.variable3 = SomeClass("Jamie")

  println("second check:")
  sample.checkVariable()
  println("---")

  sample.variable2 = SomeClass("Maddie")

  println("third check:")
  sample.checkVariable()
  println("---")

  sample.variable1 = SomeClass("Lisa")

  println("fourth check:")
  sample.checkVariable()
  println("---")
}

We can see that as each variable is set, other variables are not called, as they are listed lower in the when statement.

first check:
---
second check:
Jamie
---
third check:
Maddie
---
fourth check:
Lisa
---
aSemy
  • 5,485
  • 2
  • 25
  • 51
  • IMO, replacing null-checks with `isInitialized` checks is a poor solution because it erases null-safety. At least a nullable forces you to consider whether it is safe to use and acknowledge that you checked the behavior by marking it with `!!`. If you aren’t initializing a `lateinit` property at the class entry-point, I don’t think it should be used at all, because you are pushing your error checking to runtime instead of compile time. – Tenfour04 Nov 18 '22 at 14:45
  • @Tenfour04 It completely depends on the situation. Given the limited context that OP provided, it's impossible to say what is a poor solution or not. There are good reasons why `lateinit var` is a language feature. – aSemy Nov 18 '22 at 15:05
  • I think the good reason to use it is when your class entry point is not the constructor because your class is a subclass of something that does initialization of subclasses somewhere besides the constructor. If you are having to check `isInitialized` in random places outside the class entry point, I believe it is a misuse of `lateinit`. Just an opinion--everyone is free to ignore the null-safety features of Kotlin if they want to. – Tenfour04 Nov 18 '22 at 15:09