【问题标题】:java.lang.Integer cannot be cast to java.lang.Long in Kotlin (when the initial value is null)java.lang.Integer 在 Kotlin 中不能强制转换为 java.lang.Long(当初始值为 null 时)
【发布时间】:2016-07-01 10:30:52
【问题描述】:

如果我有以下内容,它可以工作(即数字得到分配 1000)

fun main(args: Array<String>) {
    var number: Long ? = null // or number = 0
    val simpleObject = SimpleClass()
    number = 1000
    println("Hi + $number")
}

如果我有以下内容,它可以工作(即数字得到分配 1000)

import java.util.*

fun main(args: Array<String>) {
    var number: Long = 0
    val simpleObject = SimpleClass()
    number = simpleObject.getValue<Long>()
    println("Hi + $number")
}

class SimpleClass() {
    fun <T>getValue(): T {
        return 1000 as T
    }
}

但如果我有以下,它会失败

import java.util.*

fun main(args: Array<String>) {
    var number: Long? = null
    val simpleObject = SimpleClass()
    number = simpleObject.getValue<Long>()
    println("Hi + $number")
}

class SimpleClass() {
    fun <T>getValue(): T {
        return 1000 as T
    }
}

报告的错误在number = simpleObject.getValue&lt;Long&gt;()

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long 

为什么当我初始化var number: Long ? = nullvar number: Long = 0 有不同的结果?我是不是搞错了什么?

更新

使用下面的解决方法,结果还可以。但是使用了一个额外的临时变量。

import java.util.*

fun main(args: Array<String>) {
    var number: Long? = null
    val simpleObject = SimpleClass()
    val temp = simpleObject.getValue<Long>()
    number = temp
    println("Hi + $number")
}

class SimpleClass() {
    fun <T>getValue(): T {
        return 1000 as T
    }
}

【问题讨论】:

    标签: kotlin


    【解决方案1】:

    让我们看看生成的字节码:

    fun <T> getValue(): T {
        return 1000 as T
    }
    
    // becomes
    
    public final getValue()Ljava/lang/Object;
       L0
        LINENUMBER 17 L0
        SIPUSH 1000
        INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
        CHECKCAST java/lang/Object
        ARETURN
       L1
        LOCALVARIABLE this LSimpleClass; L0 L1 0
        MAXSTACK = 1
        MAXLOCALS = 1
    

    如您所见,此方法不会将1000 转换为Long,它只是确保对象的类型为java/lang/Object(嗯,确实如此)并返回1000 作为 Integer 对象。

    因此,您可以使用任何类型调用(注意:仅调用)此方法,并且不会抛出异常。但是,将结果存储在变量中会调用真正的强制转换,这可能会导致 ClassCastException

    fun f3() {
        val simpleObject = SimpleClass()
        // L0
        //   LINENUMBER 16 L0
        //   NEW SimpleClass
        //   DUP
        //   INVOKESPECIAL SimpleClass.<init> ()V
        //   ASTORE 0
    
        simpleObject.getValue<SimpleClass>() // no problems
        // L1
        //   LINENUMBER 17 L1
        //   ALOAD 0
        //   INVOKEVIRTUAL SimpleClass.getValue ()Ljava/lang/Object;
        //   POP
    
        val number = simpleObject.getValue<SimpleClass>() // throws ClassCastException1
        // L2
        //   LINENUMBER 18 L2
        //   ALOAD 0
        //   INVOKEVIRTUAL SimpleClass.getValue ()Ljava/lang/Object;
        //   CHECKCAST SimpleClass
        //   ASTORE 1
    
    
        // L3
        //   LINENUMBER 19 L3
        //   RETURN
        // L4
        //   LOCALVARIABLE number LSimpleClass; L3 L4 1
        //   LOCALVARIABLE simpleObject LSimpleClass; L1 L4 0
        //   MAXSTACK = 2
        //   MAXLOCALS = 2
    }
    

    但是为什么将结果存储为Long? 会引发异常?再来看看字节码的区别:

    var number: Long? = null              |    var number: Long = 0
                                          |
          ACONST_NULL                     |        LCONST_0
          CHECKCAST java/lang/Long        |        LSTORE 0                
          ASTORE 0                        |
    
                    number = simpleObject.getValue<Long>() [both]
    
          ALOAD 1                         |
                  INVOKEVIRTUAL SimpleClass.getValue ()Ljava/lang/Object; [both]
          CHECKCAST java/lang/Long        |        CHECKCAST java/lang/Number
          ASTORE 0                        |        INVOKEVIRTUAL java/lang/Number.longValue ()J
                                          |        LSTORE 0
    

    如您所见,number: Long 的字节码将函数结果转换为 Number,然后调用 Number.longValue 以将值转换为 Long(Java 中为 long

    但是,number: Long? 的字节码将函数结果直接转换为Long?(Java 中的Long),这导致ClassCastException

    不确定,如果此行为记录在某处。但是,as 运算符执行不安全的强制转换,编译器会发出警告:

    Warning:(21, 16) Kotlin: Unchecked cast: kotlin.Int to T
    

    【讨论】:

    • 感谢您提供生成的 2 个 dex 代码背后的出色详细技术原因。这很好解释。我认为 Kotlin 语言应该改进它可以直接转换为 java/lang/Number 而不是 java/lang/Long 的地方。所以简而言之,它们只是不同而已。支持您的答案并勾选接受它。谢谢!!
    • 你认为这是预期的行为吗?
    • @voddan 不确定。这种行为乍一看可能并不明显,但是当开发人员说“嘿,我知道,这个演员表不安全,但相信我”时,Kotlin 应该怎么做?它警告可能的类转换异常,这是正确的。它可能应该解释警告背后的一些低级原因,或者至少提供一个文档链接(万岁的 Rust 有很棒的编译错误解释)
    • 从 Int 到 Double 的强制转换永远不会成功
    • @voddan 当然,我只是在谈论警告消息的改进
    【解决方案2】:
    return 1000 as T
    

    是未经检查的强制转换,编译器会发出警告。你总是返回一个整数,但你假装它是一个 T。只有当 T 实际上是整数时才会正常工作。在所有其他情况下,它将失败。

    如果你选择一个完全不相关的类型,比如我不知道,StringBuilder,问题的明显性会更加明显:

    var number: StringBuilder? = null
    val simpleObject = SimpleClass()
    number = simpleObject.getValue<StringBuilder>()
    

    现在您应该意识到这没有意义:您正在调用一个本应返回 StringBuilder 的方法,但它返回 1000。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-21
      • 2019-12-19
      • 1970-01-01
      • 2011-06-06
      • 2020-10-11
      • 2021-02-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多