Safe cast vs cast to nullable - kotlin

What is the difference between
x as? String
and
x as String?
They both seem to produce a String? type. The Kotlin page doesn't answer it for me.
UPDATE:
To clarify, my question is:
What is the purpose of having an as? operator at all, since for any object x and for any type T, the expression x as? T can be (I think) rephrased as x as T? ?

The difference lies in when x is a different type:
val x: Int = 1
x as String? // Causes ClassCastException, cannot assign Int to String?
x as? String // Returns null, since x is not a String type.

as? is the safe type cast operator. This means if casting fails, it returns null instead of throwing an exception. The docs also state the returned type is a nullable type, even if you cast it as a non-null type. Which means:
fun <T> safeCast(t: T){
val res = t as? String //Type: String?
}
fun <T> unsafeCast(t: T){
val res = t as String? //Type: String?
}
fun test(){
safeCast(1234);//No exception, `res` is null
unsafeCast(null);//No exception, `res` is null
unsafeCast(1234);//throws a ClassCastException
}
The point of the safe cast operator is safe casting. In the above example, I used the original String example with integers as the type. unsafeCast on an Int of course throws an exception, because an Int is not a String. safeCast does not, but res ends up as null.
The main difference isn't the type, but how it handles the casting itself. variable as SomeClass? throws an exception on an incompatible type, where as variable as? SomeClass does not, and returns null instead.

Related

Why doesn't Kotlin treat numbers as "Int" by default inside sumOf function lambda?

In the below code:
val sum = listOf(1, 2, 3).sumOf { if (it % 2 == 0) 1 else 0 }
Kotlin gives the following error:
Kotlin: Overload resolution ambiguity:
public inline fun <T> Iterable<TypeVariable(T)>.sumOf(selector: (TypeVariable(T)) -> Int): Int defined in kotlin.collections
public inline fun <T> Iterable<TypeVariable(T)>.sumOf(selector: (TypeVariable(T)) -> Long): Long defined in kotlin.collections
Playground
If I explicitly use toInt(), the error is gone but I get a warning of redundant call
val sum = listOf(1, 2, 3).sumOf { if (it % 2 == 0) 1.toInt() else 0 }
Why doesn't Kotlin automatically use Int here?
The spec says the following about the types of integer literals:
A literal without the mark has a special integer literal type
dependent on the value of the literal:
If the value is greater than maximum kotlin.Long value, it is an illegal integer literal and should be a compile-time error;
Otherwise, if the value is greater than maximum kotlin.Int value, it has type kotlin.Long;
Otherwise, it has an integer literal type containing all the built-in integer types guaranteed to be able to represent this value.
So integer literals like "1" doesn't have a simple type like kotlin.Int or kotlin.Long. It has an "integer literal type".
Example: integer literal 0x01 has value 1 and therefore has type ILT(kotlin.Byte,kotlin.Short,kotlin.Int,kotlin.Long). Integer literal 70000 has value 70000, which is not representable using types kotlin.Byte and kotlin.Short and therefore has type ILT(kotlin.Int,kotlin.Long).
Here are the subtyping rules of these ILTs. Importantly for your question:
∀Ti∈{T1,…,TK}:ILT(T1,…,TK)<:Ti
​
This rule basically says that ILTs work like an intersection type. For example, ILT(kotlin.Int,kotlin.Long) is a subtype of kotlin.Int and also a subtype of kotlin.Long.
Now let's look at your lambda { if (it % 2 == 0) 1 else 0 }. It returns either the literal 0 or the literal 1. These both have the type:
ILT(kotlin.Byte,kotlin.Short,kotlin.Int,kotlin.Long)
which is a subtype of kotlin.Long and kotlin.Int. Therefore, your lambda can be converted to both a (T) -> Long and a (T) -> Int, in the same way that a (T) -> Dog can be converted to a (T) -> Animal.
When you use toInt(), then only the (T) -> Int overload matches the return type, since Int is not convertible to Long implicitly.
Apparently, if you do toInt() on the whole expression, there is no redundant toInt warning:
fun main() {
val sum = listOf(1, 2, 3).sumOf { (if (it % 2 == 0) 1 else 0).toInt() }
}
Also note that the compiler looks at the lambda return type only because sumOf is annotated with OverloadResolutionByLambdaReturnType. If not for this, you would still get an ambiguity error even if you use toInt(). See Using lambda return type to refine function applicability for more info.
The reason why it is able to choose the Int overload in simple cases like:
fun foo(x: Int) {}
fun foo(x: Long) {}
fun main() { foo(43) }
is because of the "Choosing the most specific candidate" step in overload resolution. In this step, it handles built in numeric types differently, and considers Int the "most specific". However, this step happens just before "Using lambda return type to refine function applicability", and thinks that (T) -> Int and (T) -> Long are equally specific.

Kotlin : if String is null and double bang(!!) operator is used, why does it give compile error for length function?

example:
fun main(){
var userInput: String?
//userInput = null
userInput = "asbdef"
var inputLength:Int? = userInput!!.length
println("Length of the string is :"+inputLength)
}
Output :
Length of the string is :6
fun main(){
var userInput: String?
userInput = null
//userInput = "asbdef"
var inputLength:Int? = userInput!!.length
println("Length of the string is :"+inputLength)
}
Output :
Unresolved reference: length
I want to know why it gives compile error?
If I just replace (!!) operator with (?) it compiles well but prints output as null.
PS: I'm newbie in Kotlin
The ?. operator short-circuits if the left side evaluates to null. So the result of nullVariable?.length is null and .length is never evaluated. Your first example is effectively doing:
println("Length of the string is :" + null)
The !! operator says "throw a NullPointerException if the left side is null. Otherwise, we know it's not null on the right side". You can see this if you change your second example a little:
val userInput: String? = null
val inputLength:Int = userInput!!.length // throws NullPointerException
However, I'm not sure why are you are getting Unresolved reference: length. It looks like that the compiler is doing some optimization when you assign null directly to userInput, so rather than compiling it to a String? which throws an NPE at runtime, it knows the value is only null (not String?) at compile time, and therefore can't have the length property because null is not a reference.
You can force it to give you to NullPointerException by adding a layer of abstraction via a function call:
fun main(){
var userInput: String? = "foo"
userInput = alwaysNull()
val inputLength:Int = userInput!!.length // NullPointerException
}
fun alwaysNull(): String? = null
I don't see anything n the Null Safety documentation about the difference in behaviour between initializing in one line / via a function call vs. assigning null directly to a var though, so what's happening under the hood to get Unresolved reference is just a guess.

What is the condition that an equality operator (==) cause a compile error in Kotlin?

What is the condition that an equality operator (==) cause a compile error in Kotlin?
Comparing same types is ok, of course.
fun compare1(x: Int) = x == 1
Comparing different types cause an error:
fun compare2(x: String) = x == 1
Operator '==' cannot be applied to 'String' and 'Int'
But comparing to generic type is ok though T may be String:
fun <T> compare3(x: T) = x == 1
== can't be applied to incompatible types, i.e. such that one object can't have both types simultaneously. Note that this applies even in cases where it would actually return true:
class X(val n: Int) {
override fun equals(other: Any?) = x is Int
}
public fun main(){
println(X(0) == 0) // Operator '==' cannot be applied to 'X' and 'Int'
}
on assumption that equals "shouldn't" return true for incompatible types.
So in compare3 what matters is not that T may be String, but that it may be Int, so the comparison sometimes makes sense.

Why "!!" is required when converting the readline string value into int

I am new to kotlin, and I have been doing research on the syntax of the language. It is to my understanding that in kotlin you can cast data types using integrated functions like :
.toInt()
converting 3.14 to an integer :
3.14.toInt()
since it is known that the readline() function returns a string i am not sure why this syntax is correct:
fun main() {
println("please enter a int:")
val num1 = readLine()!!.toInt()
println("one more")
val num2 = readLine()!!.toInt()
println("sum : ${num1 + num2}")
}
and this syntax is incorrect
fun main() {
println("please enter a int:")
val num1 = readLine().toInt()
println("one more")
val num2 = readLine().toInt()
println("sum : ${num1 + num2}")
}
returns the error:
Error:(5, 26) Kotlin: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String
Just looking for a bit more of an explanation on casting and how the syntax differs when it comes to the readline() function and functions alike.
The method readLine() returns a String? - the question mark means it can either be null or a String. In Kotlin, you need to handle instances with nullable type with either ? or !! when you're invoking a method onto that instance.
The difference is that ? only proceeds when the instance is not null, and !! forces it to proceed. The latter may give you a NullPointerException.
For example:
val num1 = readLine()?.toInt()
// Here, num1 could either be a String or null
val num1 = readLine()!!.toInt()
// if it goes to this next line, num1 is not null. Otherwise throws NullPointerException
readLine() returns String? (nullable version of String?)
Function toInt() receives String (non-nullable type).
fun String.toInt(): Int // non-nullable
fun String?.toInt(): Int // nullable (call)
You must do some kind of a null check to be sure that toInt will called on a non-nullable object. The !! operator converts nullable String? type to non-nullable String.

Generalize method with nullable arguments and return type

I have a method that converts ByteArray? to base64 String? so that if argument was null output will be null as well. This is its implementation:
fun toBase64String(array: ByteArray?): String? = if(array == null) null else
Base64.getEncoder().encodeToString(array)
But when I pass in not nullable ByteArray method returns String? which is expected. Is there a way to make it generic so such use case will be possible:
val base64 = toBase64String(ByteArray(4))
where base64 will be of type String and not String? since argument was not nullable?
I just started to work with Kotlin and probably don't know language feature that can make this possible.
You can make two overloads, one for nullable ByteArray? and one for non-null ByteArray:
fun toBase64String(array: ByteArray): String =
Base64.getEncoder().encodeToString(array)
#JvmName("toBase64StringNullable")
fun toBase64String(array: ByteArray?): String? =
if (array == null) null else toBase64String(array)
We need #JvmName("...") to avoid the declaration clash in the bytecode.
Also, this allows to distinguish the functions in Java.
Usage:
val nonNullBytes: ByteArray = TODO()
val nonNullString = toBase64String(nonNullBytes) // the inferred type is String
val nullableBytes: ByteArray? = TODO()
val nullableString = toBase64String(nullableBytes) // the inferred type is String?
When the argument is of the non-null type ByteArray, the compiler will choose the overload that returns a non-null String.
Probably overloading methods is the best solution for your case, but for the sake of completeness here are two other ways to realise that using only one method (the nullable one):
Not-Null-Asserted operator:
val base64: String = toBase64String(ByteArray(4))!!
Evlis operator:
val base64: String = toBase64String(ByteArray(4)) ?: "defaultString"
if argument was null output will be null as well
If that is the only thing the function does when it encounters null argument, it's better to declare it accepting non-null values and use safe call to deal with nulls:
fun toBase64String(array: ByteArray): String =
Base64.getEncoder().encodeToString(array)
val bytes: ByteArray? = ...
val base64 = bytes?.let { toBase64String(it) }
// the same can be written with function reference instead of lambda
val base64 = bytes?.let(::toBase64String)
Here let function is called only when bytes is not null, otherwise the result of the expression is null. When called it invokes the lambda function or the function reference specified as its argument, passing ByteArray which is already checked to be non-null to that function.
Also it can be more convenient to declare toBase64String as an extension for ByteArray, so it can be invoked with safe call without the helper function let"
fun ByteArray.toBase64String(): String =
Base64.getEncoder().encodeToString(this)
val bytes: ByteArray? = ...
val base64 = bytes?.toBase64String()