Android Kotlin类强制转换异常
我是Android开发新手,我在教程中看到了这段代码Android Kotlin类强制转换异常,android,class,exception,kotlin,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?) {
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)
}
}
}
为什么会发生这种情况?在Kotlin中,
as
是一个类型转换操作符
val b = v as Button
在Java中的等价项(忽略空检查)是
此外,第一段代码中的listener
未被使用
对于第二段代码,
按钮
肯定是一个视图
,但是视图
可能不是按钮
。如果您尝试强制转换一个视图
,它实际上不是一个按钮
,您将得到该强制转换异常。这是java的基本概念。如果您仔细阅读了异常文档
抛出,指示代码试图将对象强制转换为它不是实例的子类。
简单地说,如果父类对象(v)包含子类对象实例,则只能将其类型转换为子类类型(按钮)
所以正确的代码应该是
val v: View = Button()
val b = v as Button
b.c()
as
是在Kotlin中铸造的关键字。示例:someInstance作为CastTarget
。Java等价物是(CastTarget)someInstance
。这些通常是特定于语言的,但有些语言具有相同的语法。C++和java有相同的语法(尽管它还有一个额外的语法,但这不是重点)。p>
按钮扩展视图。也就是说,按钮就是一个视图然而,这并不意味着视图是一个按钮。视图也可以是TextView、ListView、RecyclerView等。视图列表很长,还有添加更多内容的库
这意味着这是有效的:
val view: View = Button(...)
val btn = view as Button
这是因为在本例中,视图是一个按钮。但是,如果您有:
val view: View = RecyclerView(...)
val btn = view as Button
它将失败。这是因为,在本例中,出于非常明显的原因,RecyclerView不是按钮。查看(…)as按钮失败的原因是视图也不是按钮。强制转换时,只能将实例强制转换为自身或父类,而不能转换为子类。下面是一个实际的例子:
interface Base
class Parent : Base
class Child1 : Parent()
class Child11 : Child1()
class Child2 : Parent()
现在,在这种情况下,类是无用的。它们什么都不做,但仍然可以用来演示继承和铸造
现在,假设你有:
val base = getRandomBaseChild()
这是否意味着你有一个孩子2
?这里的推断类型是Base
,这意味着它可以是扩展/实现Base的任何类(或接口,因为Base是一个接口)。不一定非得是个孩子,但也可以是。由于这种情况下的方法是随机的,因此有时会失败,但并非总是如此:
val child2 = base as Child2
这是因为在某些情况下,基地实际上是一个孩子2。但对于其他任何一种情况,它都不是Child2
假设我们取而代之的是Child1:
val child1 = base as Child1
这实际上有两个有效的目标:Child1和Child11。您可以始终向下投射,但除非类型匹配,否则决不能向上投射。有了这些,您现在知道这将永远成功:
val obj = base as Any
因为一切都是Any
(/Object
在Java中)。但除非类型正确,否则升级不一定会成功
现在,如果您遇到的是这样的情况,类型实际上是不同的,最简单的方法是使用is
:
if(base is Child2) // cast and do something
或者,使用as?
有一种稍微重一点的方法。注意,这将添加一个可为空的类型;如果强制转换失败,您将得到null:
val child2 = base as? Child2 ?: TODO("Cast failed");
您还添加了一些代码;在您的示例中,您将始终能够将按钮转换为文本视图或视图,并且文本视图可以转换为视图。但是,如果将视图强制转换为文本视图或按钮,则会失败,因为类型不同
TL;医生:
视图不是按钮。要使代码正常工作,请使用val v:View=Button()
,然后强制转换<仅当声明为父类型的实例实际上是指定的子实例时,code>v才能强制转换为子实例。您还可以使用is
在强制转换之前检查类型是否匹配,或者使用as?
在失败时获取null
您还可以了解类型和继承 因为这个问题不是特定于Android的,所以让我们创建一个简单的示例 考虑以下继承层次结构,其中我们有一个水果,它有两个子类
Apple
和Banana
:
open class Fruit
class Apple: Fruit()
class Banana: Fruit()
让我们做一些测试,如果强制转换失败,它将返回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
如果您创建一个水果
,它既不是苹果
,也不是香蕉
。一般来说只是一个水果
如果您创建一个
苹果
,它就是一个水果
,因为水果
是它的超类,但是苹果
与香蕉无关,非常感谢!我是一个拥有Kotlin和类型化语言的noob,因为我来自javascript世界。我终于明白了!:)太好了!确保通过接受任何你认为有用的答案来结束问题。非常感谢你给出了这个非常详细的答案。我很感激。
val child2 = base as? Child2 ?: TODO("Cast failed");
open class Fruit
class Apple: Fruit()
class Banana: Fruit()
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