I am new to Android development and I saw this piece of code in a tutorial
class MainActivity : AppCompatActivity() {
private val newNumber by lazy(LazyThreadSafetyMode.NONE) {
findViewById<EditText>(R.id.newNumber) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val listener = View.OnClickListener {v ->
val b = v as Button
newNumber.append(v.text)
}
}
}
I tried to understand the "as" operator so i made this code:
fun main(args: Array<String>) {
open class View {
fun a() {
println("This is the View method")
}
}
open class TextView: View() {
fun b() {
println("This is the TextView method")
}
}
open class Button: TextView() {
fun c() {
println("This is the Button method")
}
}
var v = View()
var b = v as Button
b.c()
}
But I get this error:
Exception in thread "main" java.lang.ClassCastException: Simplest_versionKt$main$View cannot be cast to Simplest_versionKt$main$Button
at Simplest_versionKt.main(Simplest version.kt:28)"
Why is this happening?
as
is the keyword for casting in Kotlin. Example: someInstance as CastTarget
. The Java equivalent is (CastTarget) someInstance
. These are usually language-specific, but some languages have the same syntax for it. C++ has the same syntax as Java (although it has an extra one as well, but that's beside the point).
Buttons extend View. Which means, a button is a View. However, this does not imply a View is a button. A View can also be a TextView, ListView, RecyclerView, etc. There's a long list of Views, and there's also libraries that add more.
This means this is valid:
val view: View = Button(...)
val btn = view as Button
This works because the view is, in this case, a Button. However, if you have:
val view: View = RecyclerView(...)
val btn = view as Button
it will fail. This is because, for pretty obvious reasons in this case, a RecyclerView isn't a button. The reason View(...) as Button
fails is because a View isn't a button either. When you cast, you can only cast an instance as itself, or a parent, but not a child class. Here's an actual example:
interface Base
class Parent : Base
class Child1 : Parent()
class Child11 : Child1()
class Child2 : Parent()
Now, in this case, the classes are useless. They don't do anything, but they can still be used to demonstrate inheritance and casting.
Now, say you have this:
val base = getRandomBaseChild()
Does that imply you have a Child2
? The inferred type here would be Base
, which means it can be any class (or interface, since Base is an interface) that extends/implements Base. It doesn't have to be a Child2, but it can be. Since the method in this case would be random, this would fail some times, but not always:
val child2 = base as Child2
This is because the base will in some cases actually be a Child2. But for any of the other instances, it isn't Child2.
Say we took Child1 instead:
val child1 = base as Child1
This actually has two valid targets: Child1 and Child11. You can always downcast, but never upcast unless the type matches. With that, you now know this will always succeed:
val obj = base as Any
Because everything is Any
(/Object
in Java). But upcasting won't necessarily succeed unless the type is right.
Now, if you're in a case like this where the type actually varies, the easiest way is using is
:
if(base is Child2) // cast and do something
Alternatively, there's a slightly heavier approach using as?
. Note that this will add a nullable type; if the cast fails, you get null:
val child2 = base as? Child2 ?: TODO("Cast failed");
You also added some code; in your examples, you will always be able to cast a Button as a TextView or View, and the TextView can be cast as a View. However, if you cast a View as a TextView or a Button, it will fail because the type isn't the same.
TL;DR:
A View isn't a Button. For your code to work, use val v: View = Button()
, and then cast. v
can only be cast as a child if the instance that's declared as a parent type actually is the specified child. You can also use is
to check if the type is a match before you cast, or use as?
to get null if it fails.
You can also take a look at this article from Oracle on types and inheritance.
Since this problem is not Android-specific, let's create a minimal example.
Consider the following inheritance hierarchy, where we have a Fruit with two subclasses Apple
and Banana
:
open class Fruit
class Apple: Fruit()
class Banana: Fruit()
Let's do some testing with the safe cast operator as? which returns null
if the cast fails:
val fruit = Fruit()
fruit as? Apple // returns null - fruit is not of type Apple
val apple = Apple()
apple as? Fruit // apple is a Fruit
apple as? Banana // returns null - apple is not a Banana
If you create a Fruit
it is neither an Apple
nor a Banana
. Just a fruit in general.
If you create an Apple
, it is a Fruit
because Fruit
is its super class, but an Apple
is not related to Banana
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With