What is the scope of casted variable in Kotlin? - kotlin

When casting a variable at the right side of the assign, i'm surprisely realize that the variable still behave as the casted type and not as it was original defined.
Am i doing something wrong or it's a compiler issue?
Code:
val hippoList = listOf<Hippo>(Hippo())
val hippoMutableList : MutableList<Hippo> = hippoList as MutableList<Hippo>
hippoList.add(Hippo())
since hippoList is from a List type, it is immutable. So how does trying to run add function on an immutable type isn't cause to compilation error?

If you're doing casting it means that you know more than a compiler about this context of execution and you're telling the compiler that this hippoList is a MutableList so on every next usage of hippoList compiler already knows that this have to be a MutableList and allows you to use add method because you casted it to MutableList previously. In fact you will get a runtime error UnsupportedOperationException which means that you didn't really know more about this context of execution and you did something wrong. So instead of using casting on your own allow compiler to do it's work.
In your case instead of a casting to MutableList, transform hippoList to MutableList with
hippoList.toMutableList()
The same happens when you're using !! from nullable type to not null type, when you're using it when you know more than a compiler about the context of execution. Here's a little example
val someNullableType: String? = null
val thisStringIsNotNull = someNullableType!!
by using !! on someNullableType we're telling the compiler that someNullableType is not null as well, so we're allowed to write (as in you're case where you're telling that your List is a MutableList as well)
someNullableType.length
but we will receive exception earlier (in place where we used !! to tweak the compiler)

Related

Why is it giving a warning?

I replaced it with Val and the texts did not go away.
Edit: Changing it manually fixed it. But why "var" is it giving an error?
"Variable is never modified so it can be declared using 'val'"
In many languages, compile time validation yields information about the mutability of variables. Variables that do not change value during the execution of a function are invariables. In Kotlin (and some other languages) it is highly recommended to mark those invariables as such (by using the keyword val instead of var).
If you use val, then this has two consequences.
The obvious one is, that the value cannot change any longer. If you try to assign a new value later on, the compiler will yield a compile time error, and you must either assign the new value somewhere else, or change the val back to var. This ensures, that the original value is not accidentally changed.
{
val pi = 3.141d
for (...
pi = 3 // This will not compile
)
}
The less obvious effect is, that any reader of the scope will immediately see that the value will never change during the scope's existence. This is a great benefit in readability, as it signals any reader that variable changes happen elsewhere.
{
val myMessage = "..."
...
// Here I can be sure that myMessage is still "..."
}
It is therefore useful to mark invariables as val, as it helps read and understand the code and adds to safety in execution.
In Kotlin, var denotes a variable that can be reassigned different values, while val denotes a variable that cannot.
If you never reassign a var, it's better to show that you don't need this by using val instead. The compiler can do better optimizations and in particular smart casts with vals.
As a rule of thumb: if the compiler doesn't force you to use var, use val.

What do the brackets mean before the function in Kotlin?

What do the brackets mean before a function?
For example:
TextView myTextView = (TextView)findViewById(R.id.tvHello);
Here the (TextView) part is what I have problem with. Is it something like a parameter for
fun <T> findViewByID(...) {...}
First, some history. findViewById used to simply return View, even if the type of the found view was more specific, such as a TextView. So if you wanted to find a view and assign it to a variable with a specific type of View, you would have to cast it to that variable type.
Casting doesn't change the underlying object. It just tells the compiler that you promise that the underlying object already is that more specific type, so the compiler will let you treat it like the more specific type and assign it to a variable of that more specific type.
Your first block of code is Java. The class name in parentheses casts the following expression to a specific class. In Kotlin, you cast by using as, so it would look like:
myTextView: TextView = findViewById(R.id.tvHello) as TextView
However, in later versions of Android and in the support libraries, they changed findViewById to return a generic type T that extends View. This allows Java and Kotlin to implicitly cast the result to the type of the variable you assign the result to. So now you can just use
myTextView: TextView = findViewById(R.id.tvHello)
and it will automatically cast to TextView for you.
When you see code like your first block of code, it probably originated from old code from before the change to findViewById.
Generics are a pretty big topic, and the Kotlin documentation on it is written as if you're already highly familiar with Java generics. So I recommend reading the Java docs on it first, even if you don't really know Java. The explanation will still help you understand what generics do.
It's a cast.
That converts the result of findViewByID to TextView.
Note that with the latest kotlin API that isn't needed. findViewByID has a type parameter T and will automatically return an object of the view type you like.
Once, when that code was written, findViewByID was returning a plain View and callers had to cast it to the desired type.
In this case those brackets that contain (TextView) before the function are used to specify what kind of this you should get when you are done executing the function.
If there is a possible ambiguity about the type of thing you get when you are done executing the function, you do what is called a cast. In this case we cast the returned result to (TextView).
To understand the most basic case of casting I suggest you look at how long/double/float cast in C and Java.

Kotlin "no cast needed" in IDE

I'am new to Kotlin (and Java) so may be a stupid question, but IntelliJ keeps telling me "No cast needed" on the second function call. If i switch the order of the functions the same for the other functions.
I could imagine 2 things:
Kotlin is smart it knows: Hey first cast is fine, so i will cast the second
IntelliJ problem ?
(this as Exec).setVersionToDeploy()
(this as Exec).setEcsTaskMemory()
Both functions are defined as (Gradle-Plugin):
fun Exec.XX()
Your first guess is correct!
This is known as a smart cast: the compiler knows that, if execution reaches your second line, the type of this must be Exec (else the first line would have thrown a ClassCastException and it wouldn't have reached the second line).  So it infers the specific type, and a further cast is not needed
In general, the compiler infers types in cases such as this, so you don't need to cast explicitly.  (It's not an error to do so, only a warning; but IDEA is very keen on showing ways your code can be improved.)
You see this most commonly with nullability (since that's part of the type system).  For example, if you have a nullable field, the compiler won't let you call its methods directly:
val myString: String? = "abc"
println(myString.length) // COMPILE ERROR, as myString could be null
but if you add a manual check, the compiler smart-casts the field to its non-nullable type, so you don't need a cast:
val myString: String? = "abc"
if (myString != null)
println(myString.length) // OK; compiler infers type String

how to convert Java Map to read it in Kotlin?

I am facing some very basic problem (that never faced in java before) and might be due my lack of knowledge in Kotlin.
I am currently trying to read a YML file. So Im doing it in this way:
private val factory = YamlConfigurationFactory(LinkedHashMap::class.java, validator, objectMapper, "dw")
Best on Dropwizard guide for configurations.
https://www.dropwizard.io/1.3.12/docs/manual/testing.html
So later in my function I do this"
val yml = File(Paths.get("config.yml").toUri())
var keyValues = factory.build(yml)
When using my debugger I can see there is a Map with key->values, just as it should be.
now when I do keyValues.get("my-key")
type inference failed. the value of the type parameter k should be mentioned in input types
Tried this but no luck
var keyValues = LinkedHashMap<String, Any>()
keyValues = factory.build(yml)
The YamlConfigurationFactory requires a class to map to, but I dont know if there is a more direct way to specify a Kotlin class than with the current solution +.kotlin, like
LinkedHashMap::class.java.kotlin
Here it also throws an error.
Ideas?
Well, this is a typical problem with JVM generics. Class<LinkedHashMap> carries no info on what are the actual types of its keys and values, so the keyValues variable always ends up with the type LinkedHashMap<*, *> simply because it can't be checked at compile time. There are two ways around this:
Unsafe Cast
This is how you would deal with the problem in standard Java: just cast the LinkedHashMap<*, *> to LinkedHashMap<String, Any> (or whatever is the actual expected type). This produces a warning because the compiler can't verify the cast is safe, but it is also generally known such situations are often unavoidable when dealing with JVM generics and serialisation.
YamlConfigurationFactory(LinkedHashMap::class.java, ...) as LinkedHashMap<String, Any>
Type Inference Magic
When using Kotlin, you can avoid the cast by actually creating instance of Class<LinkedHashMap<String, Any>> explicitly. Of course, since this is still JVM, you lose all the type info at runtime, but it should be enough to tell the type inference engine what your result should be. However, you'll need a special helper method for this (or at least I haven't found a simpler solution yet), but that method needs to be declared just once somewhere in your project:
inline fun <reified T> classOf(): Class<T> = T::class.java
...
val factory = YamlConfigurationFactory(classOf<LinkedHashMap<String, Any>>(), ...)
Using this "hack", you'll get an instance of LinkedHashMap directly, however, always remember that this is just extra info for the type inference engine but effectively it just hides the unsafe cast. Also, you can't use this if the type is not known at compile type (reified).

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.