在很早的时候,小黑屋就介绍过如何研究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");
Intrinsics 是什么
Intrinsics是Kotlin内部的一个类
包含了检查参数是否为null的checkParameterIsNotNull
包含了表达式结果是否为null的checkExpressionValueIsNotNull
包含了检测lateinit是否初始化的throwUninitializedPropertyAccessException
包含了开发者强制非空!!出现空指针时抛出throwNpe
的方法
判断对象相等的方法areEqual
其他的一些处理数据异常的方法和辅助方法
所以上面代码中的Intrinsics.checkParameterIsNotNull(message, "message");
是为了检测参数message是否为null进行的判断。
为什么会有Intrinsics等判断代码呢
不是说 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
校验。
Intrinsics.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 );
}
Intrinsics.checkParameterIsNotNull 的好处
定位排查问题快捷
上面代码的好处之一就是对于代码混淆之后,可以相对更加方便的定位问题。
比如这段代码,经过混淆之后,运行
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可以辅助混淆情况下定位排查问题,但是同时也带来了一个问题,那就是
除此之外,还有人担心Intrinsics是不是存在这样的问题
Intrinsics调用和返回带来进栈出栈操作,而Intrinsics为java实现,无法在编译时inline,会不会有性能问题
对于性能的担忧可以说是有些过于杞人忧天了,不过还在好在Kotlin提供了方法来消除这种不必要的过虑。当然也能解决逆向混淆的问题。
编译时去除Intrinsics检查
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 项目设置编译选项
其他Intrinsics出现的场景
checkExpressionValueIsNotNull
当Kotlin 调用 Java 获取表达式结果后需要进行操作时,会增加Intrinsics.checkExpressionValueIsNotNull
校验
1
2
3
4
5
//Intrinsics.checkExpressionValueIsNotNull(var10000, "JavaUtil.getBook()");
fun test1 () {
val book : Book = JavaUtil . getBook ()
book . name
}
Intrinsics.throwNpe
当使用!!
非空断言时,会有校验非空断言结果的检查,如果有问题,则抛出NPE.
1
2
3
4
5
6
7
8
/**
* if (message == null) {
Intrinsics.throwNpe();
}
*/
fun test2 ( message : String ?) {
message !!. toInt ()
}
throwUninitializedPropertyAccessException
当尝试访问一个lateinit的属性时,会增加是否初始化的判断,如果有问题,会抛出异常。
1
2
3
4
5
6
7
class Movie {
lateinit var name : String
//Intrinsics.throwUninitializedPropertyAccessException("name");
fun dump () {
println ( name )
}
}
以上就是关于Kotlin编译与 Intrinsics 检查的内容。Enjoy.
相关文章推荐阅读