publicfinalclassKotClassKt{ publicstaticfinalvoidexchange(@NotNull List $this$exchange, int fromIndex, int toIndex){ Intrinsics.checkNotNullParameter($this$exchange, "$this$exchange"); int temp = ((Number)$this$exchange.get(fromIndex)).intValue(); $this$exchange.set(fromIndex, $this$exchange.get(toIndex)); $this$exchange.set(toIndex, temp); } }
classKot{ var stu: Student? = getStu() fundealStu(){ if(stu != null) { //还是不能这样写,编译器会因为可能在其他线程会修改该值有风险而报错,如果 stu 改成val 的就不会有这样的情况 print(stu.glasses) } }
}
当然,我们也能利用let来更优雅一点:
1 2 3
fundealStu(){ stu?.let {print(it.glasses)} }
在开发过程中,难免碰到类型转换,一般使用类似:
1 2 3
var stu: Student = getStu as Student? //当然,也能使用以下方法,二者效果是一样的 var stu: Student = getStu as? Student
除此之外,有些同学可能会认为需要频繁类型转换,所以会配合泛型封装一个“有效的”类型转换方法:
1
fun<T>cast(original: Any): T? = original as? T
使用上了 as? ,看起来没问题,那我们应该可以这样用:
1
val ans = cast<String>(140163L)
用法看起来也挺合理,但是在调用的时候,会抛出 Long cannot be cast to String 这样的异常。这其实是类型擦除的后果。Kotlin的设计者们同样注意到这点,加入了 reified关键字,可以理解为具体化,利用它我们可以在方法体内访问泛型指定的 JVM 对象(注意,还要在方法前加入 inline 修饰)。代码使用如下:
1
inlinefun<reified T>cast(original: Any): T? = original as? T
事实上,“继承”和“子类型化”是两个完全不同的概念。如Kotlin中的Int是Number的子类,那么在需要Number类型的地方传入 Int类型是没问题的。这是“继承”强调的“实现上的复用”。而子类型化是一种类型语义的关系,与实现没关系。虽然Any 与 Any? 看起来没有继承关系,然而在我们需要用 Any? 类型值的地方,显然可以传入一个类型为 Any 的值,反之却不然!
所以,我们可以大胆地说,Any? 是 Any 的父类型,而且是所有类型的根类型。
Any? 与 Any??。 你可能会问,那 Any??是不是 Any? 的父类型?如果成立,岂不是意味着没有所谓的所有类型的根类型了?其实,Kotlin的可空类型可以看做数学上的并集。如果用类型的并集表示 Any ,可以写成 Any U Null ,那么 Any?? 就可以写成 Any U Null U Null ,等价于 Any U Null ,即 Any??等价于 Any? 。
//子线程会间隔1s调用一次这个Runnable private val mThreadRunnable = Runnable { blockTime++ //1、标志位 mainHandlerRunEnd 没有被主线程修改,说明有卡顿 if (!mainHandlerRunEnd && !isDebugger()) { logw(TAG, "mThreadRunnable: main thread may be block at least $blockTime s") }
//2、卡顿超过5s,触发ANR流程,打印堆栈 if (blockTime >= 5) { if (!mainHandlerRunEnd && !isDebugger() && !mHadReport) { mHadReport = true //5s了,主线程还没更新这个标志,ANR loge(TAG, "ANR->main thread may be block at least $blockTime s ") loge(TAG, getMainThreadStack()) //todo 回调出去,这里可以按需把其它线程的堆栈也输出 //todo debug环境可以开一个新进程,弹出堆栈信息 } }
//run提供了一个单独的作用域,并且会返回在这个作用域当中的最后一个对象 //例如现在有这么一个场景,用户领取app的奖励,如果用户没有登录弹出登录dialog,如果已经登录则弹出领取奖励的dialog。我们可以使用以下代码来处理这个逻辑 run { if (islogin) loginDialog else getAwardDialog }.show() val original = "abc"
original.let { println("The original String is $it") // "abc" it.reversed() }.let { println("The reverse String is $it") // "cba" it.length }.let { println("The length of the String is $it") // 3 }
original.also { println("The original String is $it") // "abc" it.reversed() }.also { println("The reverse String is ${it}") // "abc" it.length }.also { println("The length of the String is ${it}") // "abc" }
在Kotlin中,主要使用lateinit 和 by lazy 这两种语法来实现延迟初始化的效果。如果这是一个用 val 声明的变量,我们用 by lazy 来修饰:
1 2 3 4 5
classBird(val weight: Double, val age: Int, val color: String) { val sex: String by lazy { if(color == "yellow") "male"else"female" } }
总结 by lazy 语法的特点如下:
该变量必须是引用不可变的,而不能通过var声明
被首次调用时,才会进行赋值操作,一旦赋值,后续将不能更改。
需要注意的是,系统会给 lazy属性默认加上同步锁,也就是 LazyThreadSafetyMode.SYNCHRONIZED ,它在同一时刻只允许一个线程对lazy属性初始化,所以,lazy是线程安全的。当然,你可以自己给lazy指定参数,如: val sex: String by lazy(LazyThreadSafetyMode.NONE)。
与lazy 不同,lateinit 主要用于 var 声明的变量,然而它不能用于基本数据类型,如 Int、Long 等,我们需要使用Integet这种包装类作为替代。lateinit 的用法如下:
1 2 3 4 5 6 7 8
classBird(val weight: Double, val age: Int, val color: String) { lateinitvar sex: String
//步长 for (i in1..10 step 2) print(i) //输出 1 3 5 7 9
//倒序 for (i in10 downTo 1 step 2) print(i) //输出: 10 8 6 4 2
//半开区间 for(i in1 until 10) print(i) //输出 123456789
用 in 来检查成员关系,,在Kotlin中我们可以用 in 关键字来检查一个元素是否是一个区间或者集合中的成员,比如:"a" in listOf ("b" , "c") ,会返回 false ;在 in 之前加上叹号就是相反结果: "a" !in listOf ("b" , "c") 返回true。更多的应用场景如下:
1 2 3 4 5 6 7
//结合范围表达式 "kot"in"abc".."xyz"
//还能通过withIndex 提供键值元祖 for ((index,value) in array.withIndex) { println("the element at $index is $value") }
中缀表达式
前面见识过 in、step、downTo、until 这些写法,都不需要通过点号,而是用中缀表达式来被调用,从而语法更直观。这是如何实现的呢?看下标准库中类似的方法 to 的设计: