I’m trying to create an extension function like this:
val MyClass?.myAttribute: String?
get() {
if(this == null){
return null
}else{
return this.performSomeOperation()
}
}
The problem with this is the following:
- I want this method to be safe when called on a
null
object (MyClass?
is intentional)
- The method
performSomeOperation()
actually never returns null
.
- Hence, the extension value
myAttribute
will only return null
if this
is null
In other words:
val str1: String = myNonNullObject.myAttribute
val str2: String? = null.myAttribute
Alas, the Kotlin compiler can’t know this semantic and will therefore complain that str1
should actually be of type String?
.
Can I enhance the get
function with a contract that says something like:
contract {
receiverNotNull() implies (result != null)
}
The code above is just a dummy to outline what I’m looking for, I never used contracts so far.
You don’t need contracts for that. Just overload the function/property like this:
val MyClass.myAttribute: String get() = this.performSomeOperation()
val MyClass?.myAttribute: String? get() = when(this) {
null -> null
else -> myAttribute // the other property is taken, thanks to smart-cast
}
The compiler will choose the most specific property, and know its type’s nullability. That is, your example will be free of compiler errors or warnings.
Right, that works too. Though I don’t see why this works: isn’t the JVM “erasure type” of both MyClass?.myAttribute
and MyClass.myAttribute
exactly the same? Usually the kotlin compiler complains when this happens.
There are no generics involved, so there is also no Erasure. It works for the same reason why this works:
fun foo(n: String) = n.toInt()
fun foo(n: Int) = n
Basic overloading of functions. Note that overloaded functions are statically bound at compile-time. So even if there were erasue, it would not matter, since erasure happens at runtime.
Update:
I see what you mean now. The JVM does not distinguish the types String? and String. So we will end-up with the same function signature twice. I don’t know how it works, but I am sure the answer is simple and can be found by decompiling the bytecode.
I know that, unlike Java, the JVM actually allows overloading methods that are different only in return type. But in our case even the return type is the same at runtime, so it must be something else.
You can use annotation to prevent signature clash:
val MyClass.attribute: String get() = performSomeOperation()
val MyClass?.attribute: String? @JvmName("getAttribute0") get() = this?.performSomeOperation()
1 Like
Oh now that’s nice! Neat trick