简体   繁体   中英

Kotlin Class Cast Exception

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.

Thrown to indicate that the code has attempted to cast an object to a subclass of which it is not an instance.

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM