首页 > 解决方案 > Kotlin 类转换异常

问题描述

我是 Android 开发新手,我在教程中看到了这段代码

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)
        }

    }
}

我试图理解“as”运算符,所以我编写了以下代码:

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()
}

但我得到这个错误:

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)"

为什么会这样?

标签: androidclassexceptionkotlin

解决方案


as是在 Kotlin 中进行强制转换的关键字。示例:someInstance as CastTarget。Java 等价物是(CastTarget) someInstance. 这些通常是特定于语言的,但有些语言具有相同的语法。C++ 具有与 Java 相同的语法(尽管它也有一个额外的语法,但这不是重点)。

按钮扩展视图。这意味着,一个按钮就是一个视图。但是,这并不意味着 View 是一个按钮。View 也可以是 TextView、ListView、RecyclerView 等。View 的列表很长,也有添加更多的库。

这意味着这是有效的:

val view: View = Button(...)
val btn = view as Button

这是可行的,因为在这种情况下,视图是一个按钮。但是,如果您有:

val view: View = RecyclerView(...)
val btn = view as Button

它会失败。这是因为,在这种情况下,出于非常明显的原因,RecyclerView 不是按钮。失败的原因View(...) as Button是因为 View 也不是按钮。转换时,您只能将实例转换为自身或父类,但不能转换为子类。这是一个实际的例子:

interface Base 
class Parent : Base 
class Child1 : Parent()
class Child11 : Child1()
class Child2 : Parent()

现在,在这种情况下,这些类是无用的。它们不做任何事情,但它们仍然可以用来演示继承和强制转换。

现在,假设你有这个:

val base = getRandomBaseChild()

这是否意味着你有一个Child2?这里推断的类型是Base,这意味着它可以是扩展/实现 Base 的任何类(或接口,因为 Base 是一个接口)。它不一定 Child2,但它可以是。由于这种情况下的方法是随机的,因此有时会失败,但并非总是如此:

val child2 = base as Child2

这是因为在某些情况下,基础实际上是 Child2。但对于任何其他实例,它都不是 Child2。

假设我们取了 Child1:

val child1 = base as Child1

这实际上有两个有效目标:Child1 和 Child11。您总是可以向下转换,但除非类型匹配,否则永远不要向上转换。有了它,你现在知道这将永远成功:

val obj = base as Any

因为一切都是AnyObjectJava 中的 /)。但是除非类型正确,否则向上转换不一定会成功。

现在,如果您遇到这种类型实际上有所不同的情况,最简单的方法是使用is

if(base is Child2) // cast and do something 

或者,有一种使用as?. 请注意,这将添加一个可为空的类型;如果强制转换失败,你会得到 null:

val child2 = base as? Child2 ?: TODO("Cast failed");

您还添加了一些代码;在您的示例中,您将始终能够将 Button 转换为 TextView 或 View,并且 TextView 可以转换为 View。但是,如果您将 View 转换为 TextView 或 Button,它将失败,因为类型不同。

TL;博士:

视图不是按钮。为了使您的代码正常工作,请使用val v: View = Button(),然后进行转换。v如果声明为父类型的实例实际上是指定的子类型,则只能强制转换为子类型。您还可以is在强制转换之前检查类型是否匹配,或者as?如果失败则用于获取 null。


您还可以查看Oracle关于类型和继承的这篇文章。


推荐阅读