slightly connected Q: the return type of a Java me...
# announcements
n
slightly connected Q: the return type of a Java method
getFoo
which returns an object of type
Foo
(and never returns
null
) is
Foo!
. Should I (a) ignore this and use
val foo = getFoo()
(b) use
val foo: Foo = getFoo()
or (c)
val foo = getFoo()!!
🅱️ 6
n
I usually do (b)
also the vote's rigged, there's no
C
emoji 😛
n
I also tend to lean towards (b) because (c) looks hacky. Also, (b) is still proper Kotlin code even after
getFoo
was re-implemented in Koltin
c
I use C because its more flexible. i don’t specify types where it does not add value
e
I go with ⓐ if I'm confident the Java does not return null,
requireNotNull(getFoo()) { "message" }
or
checkNotNull(getFoo()) { "message" }
if I'm not
c
A produces a warning in idea
n
yes, and the warning is I think well deserved because it defeats the nullability promise. All 3 will lead to a runtime error if
getFoo
ever breaks it's promise and returns
null
. but (b) and (c) at least then throw the exception at the definition instead of at the first usage.
my rule of thumb: for class properties go with (b) and for local variables with (a) or (b) depending on the scope size and how far apart the first usage is from the definition.
e
even with ⓑ you can easily trick kotlinc: https://pl.kotl.in/DnlIWPwA6
you're always going to have to deal with the potential for NPE at use site unless you put an explicit check in. I would rather write explicit messages on the checks I care about, and with most things covered, figuring out what's null from the backtrace of the other cases is much easier than the average Java codebase
n
WTF: yes,but that is IMHO a bug in the Kotlin compiler implementation. (BTW: took me a few seconds to realize that moving `foo`below
_foo
is what is required to make that work as I expected).
e
it's a "bug" that's not fixable (at least, not without performance cost) as long as Kotlin uses Java's object model and initialization
n
why is that? Seems a relatively straight-forward "use before (proper) initialization" case. data flow analysis at compile time could figure that out, no?
e
initializer can call virtual methods
e.g.
getFoo()
defined in a parent class
which could very well be in another module
n
is the probelm not in the order of `_foo`vs. `getFoo()`: enforcing that
_foo
is defined before it's first use should do the trick, no? (And yes: I realize that this could break existing code)
e
n
Ok, you got me. This is happening because order is: 1. Foo constructor 2. Bar constructor 3. Foo initializtion 4. Bar initialization correct? And why is that not a bigger problem? or was that a case of me being both ignorant and lucky?
e
Kotlin construction order: super constructor, primary constructor, init blocks in order of appearance, then secondary constructor
in this case:
Foo._foo
is initialized first, then
Foo.foo
which calls
Bar._foo
which is not yet initialized, then finally
Bar._foo
I don't know what your background is, but I can understand this behavior being surprising if you come from C++ - the object identity during the superconstructor is the superclass, so it won't call the derived class's methods. (which leads to different pitfalls, but anyway) however, Kotlin's behavior is Java's behavior, that's just how the JVM works
p
use a @Nullable / @Nonnull annotation that is processed by the kotlin compiler: https://kotlinlang.org/docs/java-interop.html#nullability-annotations
that will fix the return type to not be Foo! but Foo