Reputation: 60081
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
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
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