在很早的时候,小黑屋就介绍过如何研究Kotlin,其中涉及到了查看字节码和反编译成Java代码的方式,相信很多人研究过的人,都会或多或少遇到过Intrinsics.checkParameterIsNotNull
这样或者类似的代码。
首先,我们先看一下这段简单的方法
1 2 3 | fun dumpStringMessage(message: String) { println("dumpStringMessage=$message") } |
---|
按照我们之前的方法,反编译成Java代码就是这样的
1 2 3 4 5 6 | public static final void dumpStringMessage(@NotNull String message) { Intrinsics.checkParameterIsNotNull(message, "message"); String var1 = "dumpStringMessage=" + message; boolean var2 = false; System.out.println(var1); } |
---|
反编译后,我们可以看到代码中有这样的一行代码Intrinsics.checkParameterIsNotNull(message, "message");
checkParameterIsNotNull
checkExpressionValueIsNotNull
throwUninitializedPropertyAccessException
throwNpe
的方法areEqual
所以上面代码中的Intrinsics.checkParameterIsNotNull(message, "message");
是为了检测参数message是否为null进行的判断。
不是说 Kotlin 是空指针安全,有可空(Any?)和不可空(Any)的类型么,我上面的代码声明的是message: String
又不是message: String?
,为什么还要多此一举呢?
是的,你的这句话基本上没有毛病,但是有一个前提,那就是空指针和两种类型的特性,目前只在纯kotlin中生效,一旦涉及到和Java交互时,就不灵了。
比如我们在Java代码中这样调用,不会产生任何编译的问题。
1 2 3 4 5 | public class JavaTest { public void test() { StringExtKt.dumpStringMessage(null); } } |
---|
但是当我们运行时,就会报出这样的错误
1 2 3 4 5 6 7 | Exception in thread "main" java.lang.IllegalArgumentException: Parameter specified as non-null is null: method StringExtKt.dumpStringMessage, parameter message at StringExtKt.dumpStringMessage(StringExt.kt) at JavaTest.test(JavaTest.java:5) at MainKt.main(Main.kt:3) at MainKt.main(Main.kt) Process finished with exit code 1 |
---|
所以考虑到方法被Java调用的情况,Kotlin会默认的增加checkParameterIsNotNull
校验。
不过好在Kotlin编译器还是足够聪明的,对于不能被Java直接调用的方法,就不会增加相关处理。
比如标记为private的方法,通常情况下,不会被java调用。
1 2 3 | private fun innerDumpStringMessage(message: String) { println("innerDumpStringMessage=$message") } |
---|
反编译成的如下代码,就没有Intrinsics.checkParameterIsNotNull
1 2 3 4 5 | private static final void innerDumpStringMessage(String message) { String var1 = "innerDumpStringMessage=" + message; boolean var2 = false; System.out.println(var1); } |
---|
上面代码的好处之一就是对于代码混淆之后,可以相对更加方便的定位问题。
比如这段代码,经过混淆之后,运行
1 2 3 4 5 | public class JavaMethod { public void callKotlin() { KotlinCodeKt.dumpMessage(null); } } |
---|
得到如下的崩溃日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | E AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.droidyue.intrinsicsmattersandroidsample/com.droidyue.intrinsicsmattersandroidsample.MainActivity}: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method a.a.a.a.a, parameter message E AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2927) E AndroidRuntime: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2988) E AndroidRuntime: at android.app.ActivityThread.-wrap14(ActivityThread.java) E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1631) E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:102) E AndroidRuntime: at android.os.Looper.loop(Looper.java:154) E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6682) E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) E AndroidRuntime: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520) E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410) E AndroidRuntime: Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method a.a.a.a.a, parameter message E AndroidRuntime: at com.droidyue.intrinsicsmattersandroidsample.b.a(Unknown Source) E AndroidRuntime: at com.droidyue.intrinsicsmattersandroidsample.a.a(Unknown Source) E AndroidRuntime: at com.droidyue.intrinsicsmattersandroidsample.MainActivity.onCreate(Unknown Source) E AndroidRuntime: at android.app.Activity.performCreate(Activity.java:6942) E AndroidRuntime: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1126) E AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2880) E AndroidRuntime: ... 9 more |
---|
这里我们可以清晰的看到出问题的参数名称,定位出问题的位置。
刚才我们提到了Intrinsics可以辅助混淆情况下定位排查问题,但是同时也带来了一个问题,那就是
除此之外,还有人担心Intrinsics是不是存在这样的问题
对于性能的担忧可以说是有些过于杞人忧天了,不过还在好在Kotlin提供了方法来消除这种不必要的过虑。当然也能解决逆向混淆的问题。
1 2 | -Xno-param-assertions Don't generate not-null assertions on parameters of methods accessible from Java -Xno-receiver-assertions Don't generate not-null assertion for extension receiver arguments of platform types |
---|
具体的实施方法,可以参考另一篇文章为 Kotlin 项目设置编译选项
当Kotlin 调用 Java 获取表达式结果后需要进行操作时,会增加Intrinsics.checkExpressionValueIsNotNull
校验
1 2 3 4 5 | //Intrinsics.checkExpressionValueIsNotNull(var10000, "JavaUtil.getBook()"); fun test1() { val book: Book = JavaUtil.getBook() book.name } |
---|
当使用!!
非空断言时,会有校验非空断言结果的检查,如果有问题,则抛出NPE.
1 2 3 4 5 6 7 8 | /** * if (message == null) { Intrinsics.throwNpe(); } */ fun test2(message: String?) { message!!.toInt() } |
---|
当尝试访问一个lateinit的属性时,会增加是否初始化的判断,如果有问题,会抛出异常。
1 2 3 4 5 6 7 | class Movie { lateinit var name: String //Intrinsics.throwUninitializedPropertyAccessException("name"); fun dump() { println(name) } } |
---|
以上就是关于Kotlin编译与 Intrinsics 检查的内容。Enjoy.