前言
本篇主要记录一些日常用到的Kotlin开发技巧
空安全处理
在Kotlin中, 最出名的特性莫过于就是它的空安全了, 毕竟NPE应该是大家最不想看到的错误信息. 我们先回顾下Kotlin如何处理空安全
我们有四种方法来避免NPE
在条件中检查null
安全调用使用?.
使用Elvis 操作符 ?:
使用!!操作符
当然关于第四点使用!!操作符, 他的本质就回归到了当遇到null的时候仍然会抛出NPE. 所以在非必须的情况下, 我们应该尽量避免使用!!
为了避免NPE, 在kotlin的类型系统中, 它做到的就是强制开发者明确定义目标类型是否是可空类型(通过?区别), 如果一个变量是可空的, 我们需要这样写
var nullpossible: String? = null
而像下面这种, 是永远不会编译通过的
var nullpossible: String = null
当我们在定义一个变量的时候, 当能够确保他是非空类型的时候就必须要在构造器中初始化, 然而这在实际开发中是非常不方便的.
var a: String = ...
我们可以利用几种方式来解决
使用lateinit延迟初始化
使用委托Delegates.notNull()
var test: String by Delegates.notNull()
lateinit var testinit: String
不管我们使用哪种方式, 都可以让我们避免在初次定义类型的时候就必须初始化工作, 当然不论哪种方式, 在初始化前调用属性都是会抛出异常的.
而关于Delegates.notNull(), 通过源码我们可以看到他实际返回的是NotNullVar的委托.而通过NotNullVar中的getValue()返回直接定义为非空属性.
public object Delegates {
public fun <T: Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()
}
private class NotNullVar<T: Any>() : ReadWriteProperty<Any?, T> {
private var value: T? = null
public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
}
public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
}
还有一种方式是通过委托属性by lazy, 但是他只可以修饰val, 会在第一次调用对应属性的时候进行初始化, 默认是线程安全
val testlazy: String by lazy { "fff"}
另外他可以通过传入参数来选择不同的多线程处理
@kotlin.jvm.JvmVersion
public fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
/*
* 使用同步锁确保只有一条线程可以进行实例化
*/
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
/*
* 同一时期多个线程可以初始化实例,但是只有最先返回的值会作为延迟初始化的实例,使用 AtomicReferenceFieldUpdater.compareAndSet() 方法实现。
**/
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
/**
* 没有任何线程安全保证与开销
*/
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
单例模式的实现
Kotlin提供了object来很方便的支持了单例模式的实现
object Singleton {
fun test(){
// ...
}
}
// kotlin中调用
Singleton.test()
// java中调用
Singleton.INSTANCE.test();
我们看下他转为Java代码后是如何实现的.
public final class Singleton {
public static final Singleton INSTANCE;
public final void test() {
}
static {
Singleton var0 = new Singleton();
INSTANCE = var0;
}
}
很好, 一个典型的饿汉式. 饿汉式的缺点我们简明讲下, 由于是类加载的第一时间就会新建实例, 所以当我们整个工程没有用到的时候, 就会导致内存空间的浪费.另外, 它无法自定义构造函数. object 如果我们不适用object呢, 应该如何实现单例模式?
我们来尝试用kotlin写一个DSL单例模式, 先看java的实现方法
public class SingletonDSL {
private static volatile SingletonDSL instance;
private SingletonDSL(){
}
public static SingletonDSL getInstance() {
if(null == instance){
synchronized (SingletonDSL.class){
if(null == instance){
instance = new SingletonDSL();
}
}
}
return instance;
}
}
class SingleDSLKotlin private constructor (){
companion object {
@Volatile private var sInstance: SingleDSLKotlin? = null
fun getInstance() = sInstance ?: synchronized(SingleDSLKotlin::class.java){
sInstance ?: SingleDSLKotlin().also { sInstance = it }
}
}
}
根据上面lazy的延迟初始化的特性(通过查看源码我们可以发现他内部也是用双重锁机制来实现的), 我们还可以更加的简单实现
class SingleDSLKotlin private constructor (){
companion object {
val INSTANCE by lazy { SingleDSLKotlin() }
}
}
当然我们也可以通过静态内部类来实现单例模式
class SingletonStaticClass private constructor(){
fun getInstance() = INSTANCE.sInstance
companion object INSTANCE{
private val sInstance = SingletonStaticClass()
}
}
这里关于构造函数的相关基础知识可参见Kotlin官网
域函数的区别
我们在前面写DSL单例的demo的时候, 用到了一个also.我们开发过程中会经常用到这几个作用域函数run, with, apply, with, also, let
要理解源码, 我们首先要搞明白inline内联函数是做什么用的.
在kotlin中, 函数也是作为一个对象存储在内存中.当我们调用一个函数的时候, VM首先去找你函数存储的位置, 然后执行函数, 最后再回到你调用函数的地方. 这会分别引入了内存空间的开销和虚拟调用运行的时间开销
而内联函数做的就是在编译期就将函数的调用替换成函数的定义.
然后我们再回头看这几个函数的作用
let
我们最开始接触的作用域函数应该就是let了, 当我们处理一个可空对象的时候, 要获取它的内部某个属性的时候, 我们一般都是通过使用?.let{}来忽略掉空对象逻辑处理情况
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
可以看到他是将自身T作为参数传入调用函数中, 然后返回最后执行的结果.
private fun descriLet(){
val des = "showValue"
val letResult = des.let {
Log.e("lettt", it)
true
}
Log.e("let result", letResult.toString())
}
运行
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
可以看出当我们使用T.run的时候, 是作为T.()扩展函数的调用块, 最后返回闭包执行的结果
private fun describeRun(){
val runResult = run{
true
}
Log.e("run result", runResult.toString())
val runResult1 = "T.run".run {
Log.e("run", this)
Log.e("length", length.toString()) // print "length: 5"
2
}
Log.e("run result2", runResult1.toString()) // print "run result2: 2"
}
also
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
also和let有点像, 但是他返回的对象与闭包执行结果没有关系, 返回的是调用对象本身
private fun describeAlso(){
val alsoResult = "also result".also {
it.reversed()
}
Log.e("also result", alsoResult) // print "also result: also result"
}
apply
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
作为T.()扩展函数调用块执行, 返回被调用对象本身
private fun describeApply(){
val applyResult = "apply".apply {
reversed()
length
}
Log.e("apply Result", applyResult) // print "apply Result: apply"
}
with
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
with需要我们传入一个参数receiver, 然后作为它的扩展函数执行闭包, 返回执行结果
private fun describeWith(){
val withResult = with("with"){
reversed()
}
Log.e("with result", withResult) // print "with result: htiw"
}