https://kotlinlang.org logo
Title
t

therealbluepandabear

02/06/2022, 2:52 AM
Which of the 3 is the best choice? 1️⃣
val myVar1: Any
2️⃣
var myVar2: Any? = null
3️⃣
lateinit var myVar3: Any
---- Also I have a minor question: I'm confused, because I never knew that you could just write
val x: Int
without initializing a value for
x
? Does the first option just compile to
var x: Int? = null
? Also how does this compile if it's a
val
?
val myVar1: Any
myVar1 = 4
e

ephemient

02/06/2022, 3:46 AM
no, it does not. the final nature of vals is not enforced during initialization (although the language prevents you from writing code that cannot be statically determined to initialize every val exactly once or reading anything before it is initialized)
this is unfortunately possible to bypass:
class X {
    val a: Int = foo()
    val b: Int = 42
    fun foo(): Int = b
}
X().a // => 0
https://youtrack.jetbrains.com/issue/KT-10455 I think it's probably not resolvable without switching to a fundamentally different initialization model incompatible with Java though
as for which is the best choice, well they're not equivalent at all and it depends on what the code there is supposed to do.
2
j

Joffrey

02/06/2022, 1:04 PM
I would definitely avoid 1️⃣ in general (
val
without initializer). I never needed it, and it unnecessarily separates initialization from declaration. Kotlin's
if
and
when
expressions allow to easily avoid the classic "declaration first + initialization in branches". You can just initialize directly. If it's too complicated, extract a function that computes and returns the value, and do
val x = computeX()
. Also,
val
in general is preferable to
var
if you don't really need to change the variable through the course of its life. The stdlib's function collection operations and the `if`/`when` expressions allow to avoid most
var
usages. As for 2️⃣ vs 3️⃣, they are not exactly the same. Option 2️⃣ is more type-safe for the use site, it doesn't "lie" about anything. You have a value that can be null or not, and you have to check for null whenever you access it. You can also set the variable back to null at anytime too, which cannot be done in 3️⃣. Option 3️⃣ is for cases where you know that you will initialize the variable to a non-null value before you will access it. It's mostly to avoid the inconvenience of checking for null every time is accessed, but it's a bit more dangerous because now the type is sort of "lying" about the nullability of the variable. I'd say it's mostly for cases where the compiler cannot know about the lifecycle of your component because the initialization order is guaranteed by some framework (for instance android or spring), but it shouldn't be overused.
e

ephemient

02/06/2022, 1:14 PM
I have had to use 1️⃣ on occasions where multiple vals needed to be set from a single computation, there's no clean way to do that
1
but otherwise I agree. lateinit is additionally useful in some other situations involving compatibility with Java dependency injection, but even if 2️⃣ and 3️⃣ generate similar results, they mean different things
j

Joffrey

02/06/2022, 1:37 PM
Do you have a real life example of 1️⃣ ? I'd be interested in seeing the use case you describe. I feel like most of the time it's cleaner if that single computation returns an instance of a class containing the multiple values you need. Or you could just repeat the branching sometimes, and it might be clearer too
k

Klitos Kyriacou

02/06/2022, 2:35 PM
One possible situation I can think of is when profiling:
val result: Double
val duration = measureTime {
    result = longCalculation()
}
j

Joffrey

02/06/2022, 4:39 PM
That's why there is
measureTimedValue
though
e

ephemient

02/06/2022, 9:58 PM
you can't destructure in property initializers
val (duration, value) = measureTimedValue { ... }
doesn't work at top- or class-level
👍 1
j

Joffrey

02/06/2022, 11:01 PM
Right, for properties it might have its uses. Still I almost never had to use it though. I guess I prefer to organize classes so that I don't need complex initialization
1