Kotlin类强制转换异常

Die*_*omo 2 android exception class 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)
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

我试图理解“ 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()
}
Run Code Online (Sandbox Code Playgroud)

但是我得到这个错误:

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)"
Run Code Online (Sandbox Code Playgroud)

为什么会这样呢?

Zoe*_*Zoe 6

as是Kotlin中投放的关键字。范例:someInstance as CastTarget。Java等效于(CastTarget) someInstance。这些通常是特定于语言的,但是某些语言具有相同的语法。C ++具有与Java相同的语法(尽管它也有一个额外的语法,但这并不重要)。

按钮扩展视图。这意味着一个按钮是一个视图。但是,这并不意味着“视图是”按钮。视图还可以是TextView,ListView,RecyclerView等。视图的列表很长,而且还可以添加更多的库。

这意味着这是有效的:

val view: View = Button(...)
val btn = view as Button
Run Code Online (Sandbox Code Playgroud)

之所以可行,是因为视图在这种情况下是一个Button。但是,如果您有:

val view: View = RecyclerView(...)
val btn = view as Button
Run Code Online (Sandbox Code Playgroud)

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

interface Base 
class Parent : Base 
class Child1 : Parent()
class Child11 : Child1()
class Child2 : Parent()
Run Code Online (Sandbox Code Playgroud)

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

现在,说你有这个:

val base = getRandomBaseChild()
Run Code Online (Sandbox Code Playgroud)

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

val child2 = base as Child2
Run Code Online (Sandbox Code Playgroud)

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

假设我们改为使用Child1:

val child1 = base as Child1
Run Code Online (Sandbox Code Playgroud)

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

val obj = base as Any
Run Code Online (Sandbox Code Playgroud)

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

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

if(base is Child2) // cast and do something 
Run Code Online (Sandbox Code Playgroud)

或者,使用稍微重一些as?。注意,这将添加一个可为空的类型。如果强制转换失败,则为空:

val child2 = base as? Child2 ?: TODO("Cast failed");
Run Code Online (Sandbox Code Playgroud)

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

TL; DR:

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


您还可以查看Oracle的有关类型和继承的本文。