Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 523
Diego Perdomo Avatar asked Sep 01 '25 00:09

Diego Perdomo


2 Answers

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.

like image 129
Zoe stands with Ukraine Avatar answered Sep 03 '25 10:09

Zoe stands with Ukraine


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.

like image 20
Willi Mentzel Avatar answered Sep 03 '25 10:09

Willi Mentzel