How does exactly as? behave in kotlin? - kotlin

I was wondering why, since if (x is MyGeneric<Int>) { ... } will crash due to type erasure, as? works. I mean, couldn't A is B be implemented as (A as? B).let { if (it == null) false else true } ? If yes, why can't is checks work against generics, if no, what does differ ?

When you cast a class that has generic types, the cast will always succeed if the generic type is the only thing that is wrong. And type erasure, which you seem to be familiar with, is the reason. At runtime, a List<String> and a List<Foo> are exactly the same thing because of type erasure so casting between them will always succeed.
So a safe cast does not protect you from getting the generic type wrong, only the class type.

as? works
No it does not. as? does not return null, even when the generic type arguments are not compatible:
val strings = listOf("x", "y")
val ints = strings as? List<Int>
The above code will compile and run without any errors. It will only throw an exception, when you actually take things out of ints, and try to use them as Ints. The reason why this happens is, as you said, type erasure.
is could have been designed to work the same way as as? - evaluating to true even when the generic type arguments are not compatible:
val strings = listOf("x", "y")
strings is List<Int> // this could be designed to evaluate to true
The designers of Kotlin decided to disallow x is Foo<T>, but allow x as? Foo<T>.
After all, "checking the type" and "type conversion" come with different expectations. If you are asking me if an instance of List<Int> is of type List<Int>, you'd be very unexpected if I said yes. On the other hand, if you are asking me to convert an instance of List<Int> to a List<String>, and I successfully did it (albeit without checking the list's contents), that's not as unexpected. After all, I did what you asked for.
Now to answer your question:
I mean, couldn't A is B be implemented as (A as? B).let { if (it == null) false else true }? If yes, why can't is checks work against generics?
It could, but this would produced some rather unexpected behaviours, as discussed in the previous paragraph. Therefore, it is designed to not allow it.

Related

Why is my "List<String>" being interpreted as "List<String>?"

class Example(private val childrenByParent: HashMap<String, List<String>>) {
private val parents: List<String> = childrenByParent.keys.toList()
fun getChildrenCount(parentPosition: Int): Int {
return childrenByParent[parents[parentPosition]].size
// error, recommends using "?." or "!!"
}
}
The compiler won't let me call size directly but I don't understand why. There are no nullable types in sight.
If I let the compiler infer the type by doing this:
val infer = childrenByParent[parents[parentPosition]]
I can see that it assumes it's a List<String>?
It seems that I'm quite confused about nullability still. Would appreciate some help. I have a feeling I'm doing something incredibly dumb, but after some searching and testing I failed at fixing this.
I would like for this function to not use ?. or even worse, !!. Is it possible? At least, using HashMap and List<String>.
HashMap.get(Object) returns null when there is no element matching the key you provided, so its return type is effectively nullable, regardless of whether the values are or not.
So unfortunately you have to account for the case in which the key doesn't exist, so your choices are either implementing a case where it doesn't, or just declaring it as non-null with !! if you are sure the key exists.
Otherwise you can use HashMap.containsKey(String) to ensure the key exists and then you can be confident that using !! on the value won't result in a NullPointerException.
However as #gidds pointed out, this is not naturally thread-safe without some more work, so it might be best to just handle the case of the key not being in the map. Also I cannot actually think of many cases where you could be sure that key exists, in which a Map is the most appropriate data structure to use.
Also, even though this is not the case here, remember that nullability is just a feature of Kotlin, so when using some classes originally written in Java, whether an element is nullable or not is unknown. The IDE will usually represent this as Type! where the single ! tells you it is a platform type.

Kotlin expression fun vs normal fun - differences

Let's assume that I have two functions which do the same stuff.
First one:
fun doSomething() = someObject.getSomeData()
Second one:
fun doSomething(): SomeData {
return someObject.getSomeData()
}
Are there any technical differences between expression functions and standard function in Kotlin excluding the way how they look?
Is compiled output the same?
Are there any advantages using one instead another?
As #Sơn Phan says, they both compile to exactly the same bytecode.
So the differences are simply about conciseness.  The expression form omits the braces and return; it also lets you omit the return type (using type inference as needed).  As the question illustrates, the expression form can be shorter — and when all else is equal, shorter tends to be easier to read and understand.
So whether the expression form is appropriate is usually a matter of style rather than correctness.  For example, this function could be on one line:
fun String.toPositiveIntegers() = split(",").mapNotNull{ it.toIntOrNull() }.filter{ it >= 0 }
But it's a bit long, and probably better to split it.  You could keep the expression form:
fun String.toPositiveIntegers()
= split(",")
.mapNotNull{ it.toIntOrNull() }
.filter{ it >= 0 }
Or use a traditional function form:
fun String.toPositiveIntegers(): List<Int> {
return split(",")
.mapNotNull{ it.toIntOrNull() }
.filter{ it >= 0 }
}
(I tend to prefer the former, but there are arguments both ways.)
Similarly, I rather like using it when the body is a simple lambda, e.g.:
fun createMyObject() = MyObject.apply {
someConfig(someField)
someOtherConfig()
}
…but I expect some folk wouldn't.
One gotcha when using the expression form is the type inference.  Generally speaking, in Kotlin it's good to let the compiler figure out the type when it can; but for function return values, that's not always such a good idea.  For example:
fun myFun(): String = someProperty.someFunction()
will give a compilation error if the someFunction() is ever changed to return something other than a String — even a nullable String?.  However:
fun myFun() = someProperty.someFunction()
…would NOT give a compilation error; it would silently change the function's return type.  That can mask bugs, or make them harder to find.  (It's not a very common problem, but I've hit it myself.)  So you might consider specifying the return type, even though you don't need to, whenever there's a risk of it changing.
One particular case of this is when calling a Java function which doesn't have an annotation specifying its nullability.  Kotlin will treat the result as a ‘platform type’ (which means it can't tell whether it's nullable); returning such a platform type is rarely a good idea, and IntelliJ has a warning suggesting that you specify the return type explicitly.
1. Compiled output
Yes the compiled output will be completely the same
2. Advantage
You usually use expression function when the body of a function is only one line of expression to make it a oneliner function. Its advantage mainly about making the code more concise. Imagine instead of all the brackets and return, you only need a = to make things done.

Cast Any to Array in Kotlin

I'm initializing a class by loading data from a Map<String, Any> in Kotlin. As this Map is gleaned directly from JSON, I don't know for certain that any given key exists, or that its value is of the type I expect. To unpack this Map safely I'm doing the following, which appears to work perfectly:
a = rawData["A"] as? String ?: ""
Some of this data is in further nested JSON, which I'm unpacking to Arrays; I've tried to do this in the same way:
b = rawData["B"] as? Array<String> ?: arrayOf<String>()
However, when I attempt this using an array (as above) IntelliJ kicks up a fuss, saying
Warning:(111, 30) Kotlin: Unchecked cast: Any? to Array<String>
Is this just the IDE getting itself in a twist or is this method genuinely unsafe for Arrays despite being seemingly perfectly safe for other types?
For any future readers of this question, to expand on the accepted answer with a solution:
To safely cast Any to an array of a particular type in Kotlin, you have to first cast to an untyped array (see zsmb13's answer above for why), and then filter that array to the desired type.
For example, to cast input: Any to an array of String instances, you would call:
val inputAsArray = (input as? Array<*>)?.filterIsInstance<String>()
I was ready to call this a bug, because Array is a reified type, meaning its generic parameter can actually be checked at runtime (unlike a List, for example). I've tried looking to see if it's been filed yet, and it turns out the compiler is actually right to show you a warning. As you can see in the response to this issue, there's still a nullability problem with casts of this kind.
val a = arrayOf("foo", "bar", null) as Array<String>
println(a[2].length)
Arrays like the one in this example are successfully cast (using as, they don't throw an exception, using as?, they don't return null), however, the cast can not ensure that this is an Array<String>, only that it's an Array<String?>.
This means that you can later read null values from a variable that is typed as an Array<String> after the cast, with no further warnings from the compiler.

Why is casting the type still necessary even though it has already been assured in this example?

Today I came across this section in the Kotlin Docs. Called "smart casts", Kotlin seems to "insert (safe) casts automatically when needed":
In many cases, one does not need to use explicit cast operators in
Kotlin, because the compiler tracks the is-checks for immutable values
and inserts (safe) casts automatically when needed:
fun demo(x: Any) {
if (x is String) {
print(x.length) // x is automatically cast to String
}
}
I don't understand what "smart casting" does in this example. There seems to be nothing that requires casting, as x will always be String and thus x.length will always work, no casting required. What is happening on the print line exactly? Thanks in advance!
x has a type Any, and that type doesn't have a .length property. However, since inside the if block, it's known that x is actually a String, it gets smart cast that type, and you can call methods and access properties of the String class on it.
The Java version of this code would look like this, you'd need an explicit cast to String even after doing the type check:
void demo(Object x) {
if(x instanceof String) {
System.out.print(((String) x).length());
}
}
This is what Kotlin simplifies for you.
Without smart casting, you would have to tell the compiler the type is actually a String, since length does not exist on Any.
println((x as String).length)
With smart casting this isn't necessary anymore.

Fix generic type to the type of the first parameter

I want to write an extension function which will be available on any type and accept parameter of the same type or subtype, but not a completely different type.
I tried naive approach but it didn't work:
fun <T> T.f(x: T) {
}
fun main(args: Array<String>) {
"1".f("1") // ok
"1".f(1) // should be error
}
It seems that compiler just uses Any for T. I want T to be fixed to receiver type.
The only way to do it requires telling the compiler what you want.
fun <T> T.f(x: T) {
}
In order to use it, you have to tell Kotlin what you want the type to be.
"1".f<String>("2") // Okay
"1".f(2) // Okay (see voddan's answer for a good explanation)
"1".f<String>(2) // Fails because 2 isn't a String
"1".f<Int>(2) // Fails because "1" isn't an Int
When you call fun <T> T.f(x: T) {} like "1".f(1), the compiler looks for a common super-type of String and Int, which is Any. Then it decides that T is Any, and issues no error. The only way to influence this process is to specify T explicitly: "1".f<String>(1)
Since all the checks are performed by the compiler, the issue has nothing to do with type erasure.
Your issue is like saying "John is 3 years older than Carl, and Carl is 3 years younger than John" ... you still don't know either of their ages without more information. That's the type of evidence you gave the compiler and then you expected it to guess correctly. The only truth you can get from that information is that John is at least 3 years old and Carl is at least 1 day old.
And this type of assumption is just like the compiler finding the common upper bounds of Any. It had two strong literal types to chose from and no ability to vary either. How would it decide if the Int or String is more important, and at the same time you told it that any T with upper bounds of Any? was valid given your type specification. So the safe answer is to see if both literals could meet the criteria of T: Any? and of course they do, they both have ancestors of Any. The compiler met all of your criteria, even if you didn't want it to.
If you had tie-breaking criteria, this would work out differently. For example, if you had a return type of T and a variable of type String receiving the value, then that would influence the decision of Type inference. This for example produces an error:
fun <T: Any> T.f2(x: T): T = x
val something: String = "1".f2(1) // ERROR
Because now the type T is anchored by the "left side" of the expression expecting String without any doubt.
There is also the possibility that this could also be an type inference issue that is not intended, check issues reported in YouTrack or add your own to get a definite answer from the compiler team. I added a feature request as KT-13138 for anchoring a specific type parameter to see how the team responds.
You can fix T to the receiver type by making f an extension property that returns an invokable object:
val <T> T.f: (T) -> Unit
get() = { x -> }
fun main(vararg args: String) {
"1".f("1") // will be OK once KT-10364 is resolved
"1".f(1) // error: The integer literal does not conform to the expected type String
}
Unfortunately "1".f("1") currently causes an error: "Type mismatch: inferred type is String but T was expected". This is a compiler issue. See KT-10364. See also KT-13139. You can vote on and/or watch the issues for updates. Until this is fixed you can still do the following:
"1".f.invoke("1")
/* or */
("1".f)("1")
/* or */
val f = "1".f
f("1")