What is actually the difference and what is the preferred to use:
variable?.let { //... }
or
if (variable != null) { //... }
What is actually the difference and what is the preferred to use:
variable?.let { //... }
or
if (variable != null) { //... }
Case 2 doesnât work with mutating properties, instead in case 1 the variable it
is always not null.
While I use the first for the reason described by fvasco (youâd have to use varable!! in many cases), I donât find it to be really clear for those new to the language. To address, I created a function whenNotNull()
, which I can use as:
whenNotNull(variable) { }
This seems slightly more self documenting and understable from my perspective.
Could you please share that funciton? There is an simple if (variable != null) statement inside?
I just made this:
fun Any?.notNull(f: ()-> Unit){
if (this != null){
f()
}
}
so we can use
var s: String? = null`
s.notNull{
println("s is not null")
}
What about this approach in comparison to 1) and 2) ?
My function is
inline fun <T:Any, R> whenNotNull(input: T?, callback: (T)->R): R? {
return input?.let(callback)
}
The version with the receiver would also work fine; just a variation on a theme.
Another option is to use an immutable copy
val copy = variable
if (copy != null) {
// copy is guaranteed to be to non-nullable whatever you do
}
I just found out the difference i was looking for, from this video: 10 Kotlin Tricks in 10 ish minutes by Jake Wharton - YouTube
When i use an if statement with a var, the var can be changed by some other thread in the middle of my if statement, whilst with ?.let {}, the var is read only once and then used inside the let closure, i suppose similarly to the copy method suggested by @Filipp.Riabchun
So as i assume all var variables in kotlin are volatile, the ?.let method is more preferred with them, while with val there is no difference between let and if
Variables in Kotlin are not any more volatile than they are in Java. They certainly donât get the volatile keyword. Kotlin does however try to be thread-safe by default though as long as you donât break your thread safety yourself (you need to provide some sort of synchronization (volatile, synchronized, locks)).
Itâs not about thread safety, itâs about null-safety. Kotlin tries to provide null-safety even when the variable could be changed from another thread. If you checked your local variable and itâs not null, nothing in the world could make it null, so Kotlin compiler allows to treat is as a non-null value. If you checked your field and itâs not null, it could become null at any moment, so if you use it after check, Kotlin still canât guarantee that NPE wonât fire, so it doesnât allow you to use it as a non-null value.
Thatâs not convenient, if you ask me. In 99.99% cases fields are not shared between threads, so I would take a theoretical risk of getting NPE over a convenience (or just introduce implicit local variable automatically). There are too many cases when Iâm copying field to local variable just so null-check would work.
This is not about thread safety as you wrote. The field could be set to null by the current thread during any random method call or whatever.
if(field != null) {
doSomething() // could set field to null
field.foo() // nobody can guarantee here that field is != null
}
And automatically copying to a local variable doesnât make sense here because it changes semantics in case you want to change the field somewhere.
I took the liberty to update your code:
fun <T : Any> T?.notNull(f: (it: T) -> Unit) {
if (this != null) f(this)
}
This way if you have something like this, it will compile:
val someString: String? = null
someString.notNull { doSomething(it) }
fun doSomething(param: String) {
// do your stuff here
}
Do we really need to declare concrete type here T:Any?
We do because we want T to be a non-nullable type (since we define nullability elsewhere in the call). If we allowed for âAny?â, thereâd be no way to define that the parameter of the callback was non-null.
hiding features of the language in macros/functions is never a good thing. the v?. notation MUST be understand as âa=b++â or âc=*p++â is in C.
you are obviously free to do what you want in your codebase but I would recommend that you stick to
value?.let { it.doIt() }
over your own constructs that do the same. The reason is that your version is only more legible to you until you become more familiar with kotlin. At that point ?.let will be an idiom that you understand simply from scanning over the code while your thing becomes friction that you have to step into to see if itâs even doing what you think it does.
I wouldnât sweat
if( value != null)
vs
value?.let{}
too much and just do whatâs most natural at that point in the code.
Just a quick note that moving forward, we would want custom helpers like those above to include contracts:
contract {
(input != null) implies callsInPlace(callback, InvocationKind.EXACTLY_ONCE)
(input == null) implies callsInPlace(callback, InvocationKind.NEVER)
}
Unfortunately, the current contract DSL doesnât support any of this; I donât see any reasonable contract that can be expressed right now besides
contract {
callsInPlace(callback, InvocationKind.AT_MOST_ONCE)
}
Remember that contracts in many ways work in a reverse way. They are logic inference rules dressed up as Kotlin-like code. The problem with the contract you state is that callsInPlace is not detectable. It is an input contract. The null-value is a result contract in that for example if you see a null result you must have had a null input, or if you have a null input you will have a null output.
Iâm aware. Kotlin contracts currently donât support what I wanted to express, as Iâve said.
As a consequence, we get strictly less from a helper like whenNotNull
then from the unfortunate ?.let
idiom, as the compiler wonât know whether the lambda was executed.
just variable is too simple
check this out
getValue()?.let{value->... }
try to write similar in old style to see the difference
The actual difference is whether the receiver object is copied or not.
â?.letâ copies and qualifies its receiver as val. So we can treat the copy of receiver as immutable copy. When you only want to use the information of the receiver as nonnull value and donât have to modify it, â?.letâ is suitable.
On the other hand, âif not nullâ does not capture the receiver and canât ensure the existence of variable because it can be set to null concurrentlly. So I think âif not nullâ can often be redundant check.