破解Kotlin——绝对空安全 发表于 2022-05-29 更新于 2022-05-29
字数总计 814 阅读时长 3分钟 阅读量
空安全 首先我们说明什么是空安全。
在JVM中,有一个异常名为NullPointerException
(简称NPE),翻译过来就是空指针异常,这种异常通常出现在我们访问null
的指针的情况。而空安全就是指不会出现访问空指针的情况,从而避免NPE的出现。
在Java开发中,NPE的存在可以说是屡见不鲜了,Kotlin通过将可空与不可空分为两种不同的类型而很大程度上讲NPE的出现从运行期转到了编译期,降低了排错难度。
用法 Kotlin中有一个运算符为?.
,其作用就是访问一个可空类型,如果指针为null
就返回null
,否则返回结果。
比如下面这段代码(注释对应每一个输出的输出内容):
1 2 3 4 5 6 7 8 fun main ( ) { val obj1: String = "123A" val obj2: String? = "123A" val obj3: String? = null println ( obj1? . lowercase ( ) ) println ( obj2? . lowercase ( ) ) println ( obj3? . lowercase ( ) ) }
多线程环境 这个运算符即使在多线程环境下仍然能够保证空安全,比如下面这段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 var obj: String? = "abc" fun main ( ) { Thread { var tmp: String? = null while ( true ) { obj = tmp. apply { tmp = obj } } } . start ( ) while ( true ) { println ( obj? . uppercase ( ) ) } }
这段代码无论如何都不会抛出空指针异常。
底层解析 可能很多人看到这里还没感觉什么,但是如果我们把上面的代码改成下面这样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var obj: String? = "abc" fun main ( ) { Thread { var tmp: String? = null while ( true ) { obj = tmp. apply { tmp = obj } } } . start ( ) while ( true ) { if ( obj != null ) println ( obj!! . uppercase ( ) ) else println ( null ) } }
尝试运行后就会发现,很快程序便抛出了空指针异常。这是为什么呢?
因为多线程环境中,当计算机执行完if (obj != null)
后其它线程仍然可以修改obj
的值。也就是说,在我们执行完判空if
后,指针的值才变成了null
。
那么这种问题Kotlin的?.
是如何避免的呢?我们看一下?.
反编译的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public final class TestKt { @Nullable private static String obj = "abc" ; @Nullable public static final String getObj ( ) { return obj; } public static final void setObj ( @Nullable String var0) { obj = var0; } public static final void main ( ) { ( new Thread ( ( Runnable ) null . INSTANCE ) ) . start ( ) ; while ( true ) { String var10000 = obj; String var0; if ( var10000 != null ) { var0 = var10000; String var1 = var0. toLowerCase ( Locale . ROOT ) ; Intrinsics . checkNotNullExpressionValue ( var1, "this as java.lang.String).toLowerCase(Locale.ROOT)" ) ; var10000 = var1; } else { var10000 = null ; } var0 = var10000; System . out. println ( var0) ; } } public static void main ( String [ ] var0) { main ( ) ; } }
可以发现,Kotlin先把外界的值复制到了栈中,然后通过栈中存储的指针进行操作,简化出来的代码就是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Test { private static String obj = "abc" ; public static void main ( String [ ] args) { while ( true ) { String tmp = obj; String result = tmp == null ? null : tmp. toLowerCase ( ) ; System . out. println ( result) ; } } }
是不是非常的简单,把外部的值赋值到栈中再使用,这样子就避开了外部变动对内部的影响,无论外部怎么变动,循环中访问到的一定是同一个对象。
这在多线程开发中是一个常用的技巧,我个人认为可以归类到“保护性拷贝”之中。
破解Kotlin——绝对空安全 空 梦 | 山岳库博
更新于 2022-05-29
发布于 2022-05-29