Экспериментируя со способом компиляции Kotlin в байт-код JVM, я построил этот простой скрипт:
package testFunctions
import java.io.StringWriter
import java.io.PrintWriter
class ToHex : (Int) -> String {
override fun invoke(p: Int): String {
val e = Exception()
val stackTrace = with(StringWriter()) {
with(PrintWriter(this)) {
e.printStackTrace(this)
flush()
}
toString()
}
println(stackTrace)
return p.toString(16).toUpperCase()
}
}
typealias IntToStringFunction = (Int) -> String
fun main(args: Array<String>) {
val toHex = ToHex()
println("154 to hex: ${toHex(154)}")
val toHexLambda: (Int) -> String = { p ->
val e = Exception()
val stackTrace = with(StringWriter()) {
with(PrintWriter(this)) {
e.printStackTrace(this)
flush()
}
toString()
}
println(stackTrace)
p.toString(16).toUpperCase()
}
println("155 to hex: ${toHexLambda(155)}")
val toHexAlias = toHexLambda as IntToStringFunction
println("156 to hex: ${toHexAlias(156)}")
}
Глядя на сгенерированный байт-код, я увидел, что оба класса testFunctions.ToHex
и testFunctions.TestFunctionsKt$main$toHexLambda$1
скомпилированы для реализации kotlin.jvm.functions.Function1
и определяют общедоступный
final String invoke(int p)
метод. Поскольку этот метод явно не переопределяет абстрактный, определенный в интерфейсе, компилятор добавляет синтаксический мост с сигнатурой
/* syntethic bridge */ Object invoke(Object p)
Теперь, глядя на то, как эти вызовы связаны в основной функции, я заметил следующее поведение:
- toHex (154) компилируется с вызовом метода invoke (int p), поэтому без каких-либо ограничений;
- toHexLambda (155) и toHexAlias (156) компилируются с вызовом invoke (Object p), поэтому параметры помещаются в целочисленные ссылки перед фактическим вызовом метода.
Это поведение очевидно из стековых трасс, напечатанных в выходных данных программы:
java.lang.Exception
at testFunctions.ToHex.invoke(testFunctions.kt:8)
at testFunctions.TestFunctionsKt.main(testFunctions.kt:25)
154 to hex: 9A
java.lang.Exception
at testFunctions.TestFunctionsKt$main$toHexLambda$1.invoke(testFunctions.kt:28)
at testFunctions.TestFunctionsKt$main$toHexLambda$1.invoke(testFunctions.kt)
at testFunctions.TestFunctionsKt.main(testFunctions.kt:39)
155 to hex: 9B
java.lang.Exception
at testFunctions.TestFunctionsKt$main$toHexLambda$1.invoke(testFunctions.kt:28)
at testFunctions.TestFunctionsKt$main$toHexLambda$1.invoke(testFunctions.kt)
at testFunctions.TestFunctionsKt.main(testFunctions.kt:42)
156 to hex: 9C
Как видите, и testFunctions.kt:39
, и testFunctions.kt:42
вызывают метод синтаксического моста, который делегирует фактическую реализацию метода.
Итак, теперь мой вопрос: учитывая, что при использовании функции reified ToHex
компилятор может оптимизировать вызов, используя метод, который использует тип примитива, и учитывая, что тип функции (Int) -> String
не может принять null
В качестве входных данных, почему компилятор Kotlin не может понять, что можно избежать вызова метода syntethic bridge?