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.
In Kotlin, as
is a type cast operator.
val b = v as Button
's equivalent in Java (ignore the null check) is
Button b = (Button) v;
Besides, listener
in the first piece of code is not used.
For your second piece of code, Button
is surely a View
, but something that is a View
may not a Button
. If you try to cast a View
which is not actually a Button
, you will get that cast exception.
This is basic java concept. If you carefully read the exception documentation.
In simple words, parent class object(v) can only be typecast to child class type(Button) if it holds the child class object instance.
so Correct code would be
val v: View = Button()
val b = v as Button
b.c()
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
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.