Kotlin类强制转换异常

时间:2018-12-27 15:12:29

标签: android class exception 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)"

为什么会这样?

4 个答案:

答案 0 :(得分:4)

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

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

这意味着这是有效的:

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

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

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

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

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

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

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

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

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

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

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

TL; DR:

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


您还可以查看this article from Oracle的类型和继承。

答案 1 :(得分:0)

在Kotlin中,as是类型转换运算符。

val b = v as Button
Java中

的等效项(忽略空检查)是

Button b = (Button) v;

此外,第一段代码中的listener未使用。

对于您的第二段代码,Button当然是View,但是View可能不是Button。如果您尝试投射实际上不是View的{​​{1}},则会得到该投射异常。

答案 2 :(得分:0)

这是基本的Java概念。如果您仔细阅读了异常文档。

抛出该错误,表明代码已尝试将对象强制转换为不是实例的子类。

简单来说,如果父类对象(v)拥有子类对象实例,则只能将其转换为子类type(Button)。

所以正确的代码应该是

val v: View = Button()
val b = v as Button
b.c()

答案 3 :(得分:0)

由于此问题不是Android特有的,所以我们创建一个最小的示例。

请考虑以下继承层次结构,其中我们有一个Fruit,带有两个子类AppleBanana

open class Fruit
class Apple: Fruit()
class Banana: Fruit()

让我们用safe cast operator as?做一些测试,如果强制转换失败,返回null

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

如果创建Fruit,则它既不是Apple也不是Banana。一般来说只是水果。

如果创建Apple,则它是Fruit,因为Fruit是其超类,但是AppleBanana无关。 / p>