This question already has an answer here:
Why doesn't toString throw an exception when called on null value in Kotlin? [duplicate]
(1 answer)
Closed 3 years ago.
In my android project I have overriden onCheckedChanged() like so:
var numberOfPlayers: Int = 0
override fun onCheckedChanged(group: RadioGroup?, checked: Int) {
val chosen = activity?.findViewById<RadioButton>(checked)?.text
numberOfPlayers = chosen.toString().toInt()
}
And I'm confused why numberOfPlayers isn't underlined red as chosen may be null - therefore I'm calling toString() on a possible null value. Why won't this cause a NullPointerException?
.toString() has a safety, meaning if it receives a null value it will return "null" string.
As stated in the official documentation:
fun Any?.toString(): String
Returns a string representation of the object. Can be called with a
null receiver, in which case it returns the string "null"
Normal toString() from kotlin.Any should throw exception if value is null. But, there is also method Any?.toString() from kotlin.kotlin_builtins.
As kotlin.Any.toString cannot be applied to nullable type your compiler knows what method should use.
See this example:
fun test() {
val possibleNull: Any? = Any()
val notNull: Any = Any()
possibleNull.toString()
possibleNull?.toString()
possibleNull!!.toString()
notNull.toString()
}
If you write this in IntelliJ you'll see that the first toString() is actually extenstion method, because that one can be applied to that type. All others examples will call "normal" toString() which would work as you told.
Related
So i'm playing around will nullables and null safe in Kotlin to try understand it better.
var stringNull : String? = null
println(stringNull.toString())
println(stringNull?.lowercase())
Both of these return "null" so I'm just trying to figure out what the difference between String and String? is in practical terms. Is the "nullness" of the String not stored in the String Class itself? is it the method, such as toString() or lowerCase(), that is handling the "nullness" passed to it by an operator and not the String class itself? it's hard to see what's happening here in terms of the literal data that is being passed around. how is the value of "null", as in no data, being parsed into a string value of "null"?
The reason println(stringNull.toString()) prints null is because Kotlin decided to create an extension function on Any? called toString() over here.
If you try
class SomeClass {
fun foo() {}
}
fun main() {
val some: SomeClass? = null
println(some?.foo())
println(some.foo())
}
The compiler will complain with Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type SomeClass?
toString is just a very special function so they decided to add it on every type, hence the Any?
P.S
Also, I would recommend to use val by default (even when playing around) and only use var if you really really need to.
Is the "nullness" of the String not stored in the String Class itself?
If I understand your question correctly, that is correct, String is itself not nullable. String? is the nullable version of String.
is it the method, such as toString() or lowerCase(), that is handling the "nullness" passed to it by an operator and not the String class itself?
Among toString and lowerCase, only toString handles nulls. We can see this from their signatures:
public fun Any?.toString(): String
// ^^^^
// nullable type
public expect fun String.lowercase(): String
// ^^^^^^
// non-nullable type
In the internal implementation of toString, you can imagine that there is a bit of logic that says "if the instance is null, return "null"".
lowercase cannot be called on a nullable string directly, and will give you a compiler error if you try to do that. This is why you have to use the ?. safe navigation operator to call it on stringNull. This will check if stringNull is null before calling lowercase, and if it is found that stringNull is null, lowercase won't actually be called, and the whole expression stringNull?.lowercase() evaluates to null. Therefore, "null" is printed.
(And I think I just answered your last question)
stringNull.toString() calls the following extension function:
/**
* Returns a string representation of the object. Can be called with a null receiver, in which case
* it returns the string "null".
*/
public fun Any?.toString(): String
stringNull?.lowercase() call the following extension function:
/**
* Returns a copy of this string converted to lower case using Unicode mapping rules of the invariant locale.
*
* This function supports one-to-many and many-to-one character mapping,
* thus the length of the returned string can be different from the length of the original string.
*
* #sample samples.text.Strings.lowercase
*/
#SinceKotlin("1.5")
#WasExperimental(ExperimentalStdlibApi::class)
public expect fun String.lowercase(): String
So while the two calls both return null, it's two different function calls.
I'm new to Kotlin and these two below codes give different results.
fun main() {
var name: String? = "Rajat"
name = null
print(name?.toLowerCase())
}
Output: Compilation Error (illegal access operation)
fun main() {
var name: String? = null
print(name?.toLowerCase())
}
Output: null
When you do this assignment:
name = null
name is smart casted to Nothing?, which is problematic. Nothing is the subtype of every type, and so you become able to call any accessible extension functions of any type, according to the overload resolution rules here.
Compare:
fun main() {
var name: String? = "Denis"
name = null
print(name?.myExtension()) // works
val nothing: Nothing? = null
print(nothing?.myExtension()) // also works
}
fun Int.myExtension(): Nothing = TODO()
Note that allowing you to call any extension function on Nothing is perfectly safe - name is null anyway, so nothing is actually called.
Char.toLowerCase and String.toLowerCase happen to be two of the extension functions that are accessible, and you can call both on name, which is now a Nothing?. Therefore, the call is ambiguous.
Note that smart casts only happens in assignments, not in initialisers like var name: String? = null. Therefore, name is not smart casted to Nothing? in this case:
fun main() {
var name: String? = null
print(name?.toLowerCase()) // better to use lowercase(), toLowerCase is deprecated!
}
For the reason why, see my answer here.
The actual error on your first example is
Overload resolution ambiguity: public inline fun Char.toLowerCase(): Char defined in kotlin.text public inline fun String.toLowerCase(): String defined in kotlin.text
Looks like the Kotlin compiler is being too smart for its own good here. What's happening, is that on the second example, you are explicitly defining a variable of type String? and assigning it some value (null in this case, but that doesn't matter).
On the second example, you are defining a variable of some type, and then telling the compiler "hey, after this assignment, name is always null". So then it remembers the more-specific "name is null" instead of "name is String?".
The standard library has two methods called toLowerCase, one on Char and one on String. Both of them are valid matches now, and the compiler is telling you it doesn't know which one to pick. In the end that won't matter, because name is null, but the compiler apparently doesn't use that final thing to throw out the method call altogether.
I am trying check nullable object with extension function, but smart casting not work after calling this function.
fun <T> T?.test(): T = this ?: throw Exception()
val x: String? = "x"
x.test()
x.length // Only safe (?.) or non-null asserted (!!) calls are allowed on a nullable receiver of type String?
Is it a Kotlin bug? If not, why there is no implicit casting?
As #Madhu Bhat mentioned in comment above, your variable 'x' is still nullable.
You may use your function simply like this:
x.test().length
Otherwise you can check for null by following inline function and then perform any functions directly on the variable. (Note the usage of 'contract' and annotations '#ExperimentalContracts')
#ExperimentalContracts
fun <T> T?.notNull(): Boolean {
contract {
returns(true) implies (this#notNull != null)
}
return this != null
}
Now you can use this function like this
if(x.notNull()){
x.length
}
But its not seems so useful if your using this function just to check nullability.
Check here to know more about Kotlin contracts
In lombok extension method obj.method() is a syntax sugar for SomeUtil.method(obj). It allows for obj be null.
Kotlin extensions methods are resolved statically so I assume it's the same syntactic sugar. But when I wrote
fun Any.stringOrNull() = this?.toString()
I got a warning about unnecessary safe call on non-null receiver. Does that mean I can't call extension functions on null objects like with Lombok?
You can call it on a nullable object if you define it to be an extension on a nullable type:
fun Any?.stringOrNull() = ...
Otherwise, like with any other method, you'd have to use the safe call operator.
You can create extensions on nullable receiver types. In your example, it has to be Any? instead of Any which would not allow null, see the docs:
Nullable Receiver
Note that extensions can be defined with a nullable receiver type. Such extensions can be called on an object variable even if its value is null, and can check for this == null inside the body. This is what allows you to call toString() in Kotlin without checking for null: the check happens inside the extension function.
fun Any?.toString(): String {
if (this == null) return "null"
// after the null check, 'this' is autocast to a non-null type, so the toString() below
// resolves to the member function of the Any class
return toString()
}
Be careful, for:
fun Any?.toString(): String
following behavior:
var obj: Any? = null
obj?.toString() // is actually null
obj.toString() // returns "null" string
just spent 15 very frustrating minutes before realized this...
val string: String? = "Hello World!"
print(string.length)
// Compile error: Can't directly access property of nullable type.
print(string?.length)
// Will print the string's length, or "null" if the string is null.
?. Safe Call operator for nullable receiver##
The safe call operator returns null if the value to the left is null, otherwise continues to evaluate the expression to the right, so in order to call any function on nullable receiver you need to use safe call operator after Any.(Use Any?)
Then you can check for null value of this(here this object points to receiver) inside function body.This is what allows you to call toString() in Kotlin without checking for null: the check happens inside the extension function.
fun Any?.toString(): String {
if (this == null) return "null"
// after the null check, 'this' is autocast to a non-null type, so the toString() below
// resolves to the member function of the Any class
return toString()
}
This question already has answers here:
Smart cast to 'Type' is impossible, because 'variable' is a mutable property that could have been changed by this time
(12 answers)
Closed 4 years ago.
Error on the second println :
Smart cast to 'Boolean' is impossible, because 'r.isSquare' is a
mutable property that could have been changed by this time
fun main(args: Array<String>) {
val r: Rectangle = Rectangle(5,5)
println(r.isSquare)
r.isSquare = true
println(r.isSquare) // error but works with println(r.isSquare?:false)
}
data class Rectangle(var height: Int, var width: Int){
var isSquare: Boolean? = null
}
If it was null, it would print null like the first println, why do i have to do this ?
Edit 2
Thanks for all your answers, what i understand now :
First println is
println(message: Any?)
Second println is
println(message: Boolean)
Because r.isSquare = true make compiler trust that isSquare is Boolean and not anymore Boolean?
Edit2
Here is how i handle the compiler to keep trusting isSquare is Boolean?
fun main(args: Array<String>) {
val r: Rectangle = Rectangle(5, 5)
println(r.isSquare)
r.isSquare = true as Boolean? // if no cast, he will try wrong println signature
println(r.isSquare)
}
data class Rectangle(var height: Int, var width: Int){
var isSquare: Boolean? = null
}
Since the r.isSquare is a mutable property, the compiler cannot smart cast it to a non-null property after a null check.
You can use let:
r.isSquare.let { println(it) }
let reads the value of r.isSquare only once and it provides the same value as it inside the lambda. So you don't have to use ? or !! to access the boolean even after the null check.
From the Kotlin spec:
The language uses information about preceding checks for null, checks
for types (is, !is), safe call operators (?.) and Nothing-returning
expression to infer additional information about types of variable
(beyond that explicitly specified or inferred from initializers at
their declarations) that may be more specific in certain blocks or
even expressions. This information is then used to enable wider set of
operations on those expressions and to select more specific overloads.
fun main(args: Array<String>) {
var x : Any
x = ""
x.toUpperCase() // OK, smart cast to String
}
The first println uses this println(message: Any?)
Since you are assigning true to the isSquare next, the compiler tries to smart cast the isSquare to the Boolean type, when you try to print that. But it couldn't smart cast because the property is a mutable type.
If you remove the line, r.isSquare = true, then the compiler does not try to smart cast it to Boolean and uses the println with Any? as parameter.
In order for it to work you have to add a non null asserted call (!!) after your variable. Either r!!.isSquare or r.isSquare!!
fun main(args: Array<String>) {
val r: Rectangle = Rectangle(5,5)
println(r.isSquare)
r.isSquare = true println(r.!! isSquare)
}
data class Rectangle(var height: Int, var width: Int) {
var isSquare: Boolean? = null
}
As isSquare is a mutable property (var). It means between lines where do you write a value and then you read it, another thread can modify it and get a NPE because of that.
r.isSquare = true
//Another thread set r.isSquare = null
println(r.isSquare) // you get a null pointer exception
You must check the property nullability every time you work with nullable vars.
Because println() function in Android doesn't support Boolean?, and Boolean? with mutable property in kotlin cannot be unwrapped automatically by smart cast of Kotlin.
Try out String? or Int? or any types with mutable and nullable properties will get the same thing happen.