java.lang.Integer нельзя передать в java.lang.Long в Kotlin (когда начальное значение равно null)

Если у меня есть следующее, оно работает (т.е. номер получает назначение 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<Long>()

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

Почему при инициализации var number: Long ? = null var number: Long ? = null и var 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 } } 

Давайте посмотрим на сгенерированный байт-код:

 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 что может привести к 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 ( long in Java)

Однако, байт-код для number: Long? выводит результат функции непосредственно в Long? ( Long in Java), что приводит к ClassCastException .

Не уверен, если это поведение где-то задокументировано. Однако оператор as выполняет небезопасные действия, и компилятор предупреждает об этом:

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

это неконтролируемый отбор, и компилятор выдает предупреждение об этом. Вы возвращаете Integer, всегда, но вы притворяетесь, что это T. Это будет работать только отлично, если T на самом деле Integer. Во всех остальных случаях он потерпит неудачу.

Очевидность проблемы была бы более очевидной, если бы вы выбрали совершенно несвязанный тип, как, я не знаю, StringBuilder:

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

Теперь вы должны понимать, что это не имеет смысла: вы вызываете метод, который должен возвращать StringBuilder, но он возвращает 1000.