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.
Related
I'd like to understand a bit better the 2 functions below. I know it is very compact and understand more or less what it does: it converts each characters of a string into string of '0' and '1'. But...
How does the dot(in front of encodeToByteArray) connect the 's' to encodeToByteArray()?
Where can I find more info about what dot represents?
Also, how and why the code { byte -> binaryStringOf(byte) } can do the job in that place?
How does it "know" that there is a byte with which it calls the function binaryStringOf(byte)
Where can I find more info about it, too?
fun binaryStringOf(message: String): String {
var s: String
s = (message)
.encodeToByteArray()
.joinToString("") { byte -> binaryStringOf(byte) }
return s
}
fun binaryStringOf(b: Byte): String {
return b.toString(2).padStart(8, '0')
}
The formatting above makes things a little bit more confusing, but let me try to explain what is going on.
The = is an assignment operator. It says "assign the variable s to the result of the expression on the right side".
Now we see that message is a parameter in the binaryStringOf function of type String. String is a class which contains a function (also called a method when it is a member of a class) called encodeToByteArray which returns a ByteArray.
ByteArray in turn has a function called joinToString which we're giving two parameters: one of type String, and one of type ((Byte) -> CharSequence) (ie, the function is itself being passed in as a variable, using lambda syntax). Kotlin has some syntactic sugar to make this look nicer when the lambda is the last argument.
So, the statement
s = (message)
.encodeToByteArray()
.joinToString("") { byte -> binaryStringOf(byte) }
means "the variable s is assigned the value that results from calling joinToString on the result of calling encodeToByteArray on message.
Then return s says that the return value from the binaryStringOf should be whatever value was assigned to s.
.encodeToByteArray()
works on the incoming string (message in this case). It returns a ByteArray; so something that represents an array of Byte values.
And on that array object, it invokes the joinToString() method. That method receives various arguments, but only the separator string ("") is provided, and the transform parameter.
Now: transform is a function. It is something that can be invoked, with parameters, and that has to return a specific result.
The key part to understand is that { byte -> ... } is that transform function parameter.
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.
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.
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()
}
I have an attribute in my model defined as above that in some cases contain an Int.
var value: Any?
I know that I can do it if I cast first to String and then to Int
value.toString().toInt() // works
Is there a way of doing it by skipping casting into a String before?
When I try to cast directly to into an Int I get this error
FATAL EXCEPTION: main
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
The problem is that you were attempting to cast directly from a String to an Int with value as Int.
This isn't working because, as the exception is telling you, value contains a String, and a String cannot be coerced into an Int. The fact that this String represents an integer doesn't matter, as it would need to be parsed into an Int. That precisely is what the toInt() method does.
The reason you must cast to a String first is because toInt() is an extension method on String, and value is of type Any?. This means that you can't call toInt() directly on value because it doesn't know that it contains a String at compile time, even though it does at runtime. If you wanted to skip this step, you could also use smart casts by checking the type first:
if (value is String) {
value.toInt()
}
You can find a bit more info on smart casts and other Kotlin type casting here: https://kotlinlang.org/docs/reference/typecasts.html#smart-casts
This also may be pointing to an underlying design problem. You expect that this will sometimes contain an Int, but as we can see here whatever is setting this value in your model is setting it to a String containing that number. Do you expect this value to truly be able to contain many different types, or are you only expect Int and String? If the latter, then it would possibly be a better design decision to make this a String rather than an Any?. That would prevent unexpected/unhandled types from being provided, and make the model less ambiguous to anyone looking at or using it.
This is the way I do it:
val myInt = value as? Int ?: defaultValue
This will try to convert value to an int, but if it fails, will use defaultValue instead.
We can use Kotlin's toInt() or toIntOrNull() method to convert from String to Int type. For example:
fun giveString(): String {
return "12"
}
fun giveInt(): Int {
return giveString().toInt() // here we are casting return type to Int
}
Below we are casting return tye to Int if the string is not convertible, will return null
fun giveNullableInt(): Int? {
return giveString().toIntOrNull()
}