Given that `lateinit` can't go on primary construc...
# announcements
k
Given that
lateinit
can't go on primary constructor parameters, is there a less manual way of doing something like this (lateinited class fields that can be set through the constructor)?:
Copy code
class Foo(x: String? = null) {
    lateinit var x: String
    
    init {
        if (x != null) this.x = x
    }
    
    fun show() = println("Foo: $x")
}
s
Why exactly does
x
need to be
lateinit
in this example?
j
Copy code
class Foo(var x: String) { }
Or
Copy code
class Foo(var x: String = null!!) { }
But it smells...
o
What would be the value of
this.x
if provided
x
is
null
then? (answer: a runtime crash ๐Ÿ’ฅ)
lateinit
, the Kotlin's
NullPointerException
๐Ÿ˜… I would go either for:
Copy code
class Foo(x: String? = null) {
  val x: String = x ?: "SomeFallbackValue"
}
or
Copy code
class Foo(val x: String? = null)
This way, at compile time your know you are safe either with a valid/known/non-null fallback value or by explicitly checking for
null
when using
x
j
This is sort of a signal that what you are trying to do, is something you probably don't want to do. A late init field, that hasn't been initialised by the time it is used is a bug. You are just converting an NPE into an uninitialized exception. It is the same as a String? but the syntax shows that it should be expected to be not null, so is more confusing. I'd avoid that pattern.
k
It's two requirements basically:
x
field that's lateinit (with regular dangers that come with it) and ability to set the
x
field in the constructor (in case it's possible)
o
It totally misleading IMO, if it's optional, then type it as so with a nullable
String?
k
@James Richardson I don't think I can avoid it, either i have a
foo.x!!
all over the codebase, or
lateinit x
o
lateinit
might be more relevant on Android to initialize value in
onCreate
for instance when you know nothing will use it before Android calls you (view reference for instance)
k
the rest of the code takes care that whenever
x
is accessed is not null, so the
!!
is just noise
either that, or I could have subclasses with/without the x field, to mark it statically... but that's also too much trouble than it's worth
o
you won't use
!!
, but the checks you mentioned ๐Ÿค” either using
if (foo.x != null)
(+ smart cast) or
foor.x?.bar
k
if checks or
?.
are also redundant in huge parts of the code, because the field can't be null if I'm not mistaken, smart casts also wont work because the field is
var
not
val
, right?
o
But you are jeopardizing on all potential client code of your class.
Who guarantees your assumption that client code will do such checks, how?
How will it evolves with the future you, your next (junior?) colleague, your new call site not aware of such subtle impl of your class?
k
they'll have to manage ๐Ÿ˜„ the thing is, there's no point in
if (x !=null) doSomething(x)
because the else branch in this case has to be
error("cant continue")
... either the field is there or the program has to crash
o
foo.x?.let { doSomething(it) } ?: error("can't continue")
Or directly
doSomething(checkNotNull(foo.x) { "can't continue" })
at least it 100% explicit at compile time that you need to be careful
hence the use of nullable
with
lateinit
it's just Russian roulette depending on the mood of who uses your code
k
yeah, I'd probably go for something like that if it weren't for literally hundred of fields (so thousands of lines of code with those checks)
that would make the cure worse than disease ๐Ÿ™‚
o
a single class with hundreds of fields might be the start of your issues?! ๐Ÿ˜‰
k
that's baaaaaarely scratching the surface ๐Ÿ˜„ it's an wrapper for a undocumented REST / JSON-RPC API from hell ๐Ÿ™‚
sometimes calling the same endpoint returns different objects (probably hits different servers on the backend), or objects with fields missing, etc, etc...
o
You mentioned the subclass solution which might also be more suitable if you class is complex due to various use cases all mixed together.
Copy code
sealed class Topic {
  object Situation1 : Topic()
  data class Situation2(val x: String) : Topic()
  data class Situation3(val x: String, y: String) : Topic()
}

fun clientSide(topic: Topic) {
  when (topic) {
    Situation1 -> doBasicStuff()
    is Situation2 -> doSomething(topic.x)
    is Situation3 -> doAdvancedStuff(topic.x, topic.y
  }
}
k
yeah, the subclass variant is my favorite as well, if possible... typesafe, self documenting, IDE can help with meaningfull autocomplete, etc, etc...
o
If you can model your REST variety of situation with such type hierarchy