Elye
Elye

Reputation: 60081

java.lang.Integer cannot be cast to java.lang.Long in Kotlin (when the initial value is null)

If I have the following, it works (i.e. number get assign 1000)

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

If I have the following, it works (i.e. number get assign 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
    }
}

But if I have the below, it fails

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

The error reported is on the number = simpleObject.getValue<Long>() line

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

Why when I initialise var number: Long ? = null and var number: Long = 0 have different result? Did I get anything wrong?

UPDATED

A workaround using the below, the result is okay. But an additional temp variable is used.

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

Upvotes: 9

Views: 10832

Answers (2)

awesoon
awesoon

Reputation: 33651

Let's take a look into generated bytecode:

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

As you can see, this method does not cast 1000 to a Long it simply ensures, that the object is of type java/lang/Object (well, it is) and returns the 1000 as an Integer object.

Therefore, you may call (note: call only) this method with any type and this will not throw exception. However, storing the result in a variable invokes real cast which may lead to 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
}

But why storing the result as a Long? throws an exception? Again, let's take a look at the differences in bytecode:

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

As you can see, the bytecode for number: Long casts the function result to a Number and then calls Number.longValue in order to convert the value to a Long (long in Java)

However, the bytecode for number: Long? casts the function result directly into the Long? (Long in Java) which leads to ClassCastException.

Not sure, if this behavior documented somewhere. However, the as operator performs unsafe cast and the compiler warns about it:

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

Upvotes: 8

JB Nizet
JB Nizet

Reputation: 691635

return 1000 as T

is an unchecked cast, and the compiler issues a warning about it. You're returning an Integer, always, but you pretend it's a T. That will only work fine if T is actually Integer. In all other cases, it will fail.

The obviousness of the problem would be more blatant if you chose a completely unrelated type like, I don't know, StringBuilder:

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

Now you should realize that this doesn't make sense: you're calling a method that is supposed to return a StringBuilder, but it returns 1000.

Upvotes: 1

Related Questions