几十个常用的kotlin Tips

89 阅读7分钟

1. kotlin中没有static关键字

比如在java中的A.a(),在kotlin中反编译这个代码是A.INSTANCE.a(),这个INSTANCE就是A初始化后创建的对象。

2. 密封类(sealed class)

密封类不允许有非-private 构造函数(其构造函数默认为 private)。

比较像枚举,但是注意,密封类是子类固定,枚举是对象固定,密封类中声明的是一个个子类,而枚举声明的是一个个对象。

密封类通常会和数据类data class一起用,对多个数据类做封装。比如访问接口对返回值的处理,有成功失败等情况。

以下代码的实质是:这些Success、Error等内部类会自动继承所在的密封类Result

sealed class Result<out R> {
    data class Success<out T>(val data: T, val message: String = "") : Result<T>()

    data class Error(val exception: Exception) : Result<Nothing>()

    data class Loading(val time: Long = System.currentTimeMillis()) : Result<Nothing>()
}

用的时候,就可以根据类型判断(区别于枚举,枚举的判断条件是对象)。

fun display(data: Result) = when(data) {
    is Result.Success -> displaySuccessUI(data)
    is Result.Error -> showErrorMsg(data)
    is Result.Loading -> showLoading()
}

3. bean

bean类用data关键字表示,构造器会自动注入并且生成getter

data class Success(val resJson: String)

对应的java类

public final class Success {
   private final String resJson;

    public Success(@NotNull String resJson) {
      this.resJson = resJson;
   }
   
   @NotNull
   public final String getResJson() {
      return this.resJson;
   }
}

4. 数组和集合

listOf("one", "two", "one")是不可变集合,等于数组

var list = arrayListOf<String>()var list = mutableListOf<String>() 是可变集合

5. final

kotlin的final表示方式

val HAHAHAAH = "50"

对应的java

final String HAHAHAAH = "50";

6. static

kotlin的static表示方式

companion object {
    //const只能放在companion object里
    //const关键字的作用是改变量作用域为 public
    //若不加const关键字,则默认是private
    const val HEADER_VIEW = 0x10000111
    
    //方法默认是 public final,会放到Companion对象中
    fun onTestCompanion() {
    }
}

对应的java

public static final String HEADER_VIEW = 268435729;
public static final class Companion {
    private Companion() {
    }

    public Companion(DefaultConstructorMarker $constructor_marker) {
     this();
    }
    
    public final int getHEADER_VIEW() {
     return VCodeTimerKt.HEADER_VIEW;
    }
    
    public final void onTestCompanion() {
    }
}

7. 内部类

kotlin中的内部类默认是static final的

class Outer {
    
    class Inner {
        fun foo() = 2
    }
}

对应的java代码是

public final class Outer {

    public static final class Inner {
        public final int foo() {
            return 2;
        }
    }
}

如果想要去掉static则需要添加 inner 关键字

inner class Inner {
   fun foo() = bar
}

8. 「apply、also」、「run、with、let」 的差异与选择

/**
 * 作用域函数:let、run、with、apply 以及 also
 * 它们的唯一目的是在对象的上下文中执行代码块
 * 由于作用域函数本质上都非常相似,因此了解它们之间的区别很重要。每个作用域函数之间有两个主要区别:
 * 引用上下文对象的方式:
 * 作为 lambda 表达式的接收者(this)或者作为 lambda 表达式的参数(it)
 * run、with 以及 apply 通过关键字 this 引用上下文对象
 * let 及 also 将上下文对象作为 lambda 表达式参数
 *
 * 返回值:
 * apply 及 also 返回上下文对象。
 * let、run 及 with 返回 lambda 表达式结果.
 */

/**
 * 函数	对象引用	 返回值	       是否是扩展函数
 * let	  it     Lambda表达式结果	   是
 * run	 this	 Lambda表达式结果	   是
 * run	  -	     Lambda表达式结果	 不是:调用无需上下文对象
 * with	 this	 Lambda表达式结果	 不是:把上下文对象当做参数
 * apply this	   上下文对象	       是
 * also	  it	   上下文对象	       是
 */

/**
 * 对一个非空(non-null)对象执行 lambda 表达式:let
 * 将表达式作为变量引入为局部作用域中:let
 * 对象配置:apply
 * 对象配置并且计算结果:run
 * 在需要表达式的地方运行语句:非扩展的 run
 * 附加效果:also
 * 一个对象的一组函数调用:with
 */

apply:在apply函数范围内,可以任意调用该对象的任意方法,并返回本身对象

ArrayList<String>().apply {
    add("jimu")
}.size

also:使用 it 调用对象本身,返回值是对象本身

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"
}

run:run和with很像,可以调用对象的任意函数,返回值是最后一行

ArrayList<String>().run{
    add("jimu")
    size
}

let:默认当前这个对象作为闭包的it参数,返回值为函数最后一行,或者return

"jimu".let {
    println(it.length)
    return 0
}

//通常判空也用let:
obj?.let {
    
}

with:传一个参数,可通过this访问当前参数对象,返回值是最后一行

with(ArrayList<String>()){
    add("jimu")
    size
}

结合使用:

@Synchronized
private fun ensureViewManager(): AbsDoKitViewManager {
    return _doKitViewManager
        ?: run {
            if (DoKitManager.IS_NORMAL_FLOAT_MODE) NormalDoKitViewManager() else SystemDoKitViewManager()
        }.also {
            _doKitViewManager = it
        }
}

9. getter、setter

  • var 声明的变量,会自动生成getter和setter方法
  • val 声明的变量,只会自动生成getter方法

重写getter和setter方法:

private var Node.isExported: Boolean?
    set(value) {
        attributes()[qExportedKey] = "$value"
    }
    get() = attributes()[qExportedKey]?.toString()?.toBoolean()
    
    或者
    get() {
        return attributes()[qExportedKey]?.toString()?.toBoolean()
    }

10. 匿名内部类

创建对象加了{ },就会变成子类,由于不是直接继承且没有名字,所以是匿名类,所以要用object关键字

 var mHandler: Handler = object : Handler() {
        override fun handleMessage(msg: Message?) {
            super.handleMessage(msg)
        }
    }

11. 线程

创建线程

  Thread(Runnable {

    }).start()

12. 变量的调用

声明一个可空变量

var view: View? = null

用可空变量的调用

  • 使用 ? view?.setBackgroundColor(Color.RED)

    在java里就相当于
    if (view != null) {
        view.setBackgroundColor(Color.RED)
    } 
    
  • 使用 !! view!!.setBackgroundColor(Color.RED)

    意思是告诉编译器,我保证这里的 view 一定是非空的,编译器你不要帮我做检查了,有什么后果我自己承担。这种「肯定不会为空」的断言式的调用叫做 「non-null asserted call」。一旦用了非空断言,实际上和 Java 就没什么两样了,但也就享受不到 Kotlin 的空安全设计带来的好处(在编译时做检查,而不是运行时抛异常)了。
    

13. extension扩展使用注意

import kotlinx.android.synthetic.main.<布局的名字>.*
import kotlinx.android.synthetic.main.<布局的名字>.view.*

  • 如果是在Fragment或Activity,在平时直接findViewById的,就使用 import kotlinx.android.synthetic.main.<布局的名字>.*
  • 如果是手动创建inflate的布局,引用某控件间需要通过布局引用的,比如 View.findViewById 的,在布局名字后面跟上 .view.* import kotlinx.android.synthetic.main.<布局的名字>.view.*

14. !! 保证不为空

!! 可以抵消 ?

比如某个参数需要Drawable,但是实际上获取的是 Drawable? ,这时候可以在Drawable对象后面追加 !! ,需要告诉编辑器我这个肯定不为空

ContextCompat.getDrawable(MyApp.getCtx(), R.drawable.shape_divider)!!

15. 高阶函数

参数或者返回值函数类型的函数」,在 Kotlin 中就被称为「高阶函数」——Higher-Order Functions。

普通函数类型

clipboard.png

clipboard.png

//         (Int,  Int) ->Float 这就是 add 函数的类型
//           ↑     ↑      ↑
fun add(a: Int, b: Int): Float { return (a+b).toFloat() }

变量

var listener : ((String, Boolean) -> Unit)? = null

参数

fun b(method: (Int, String) -> String) {
  ...
}

返回值

fun c(param: Int): (Int) -> String {
    return ::a //返回值直接用 ::method 表示
}

调用传参:

//方式一:匿名函数类型
b(fun(param: Int): String {
    return ""
})

//方式二:函数类型
b(::funA)

//方式三:lambda
b { p1, p2 ->
    "aaaaaaaaa"
}

带有接收者的函数类型

clipboard.png

// 声明一个上下文是StringBuilder对象的高阶函数
fun kotlinDSL(block:StringBuilder.()->Unit){
    //调用时将对象传给这个高阶函数。也可这样写 StringBuilder("Kotlin").block()
    block(StringBuilder("Kotlin"))
}

// 调用方法,传参高阶函数
kotlinDSL {
  // 这个 lambda 的接收者类型为StringBuilder
  append(" DSL")
  println(this)
}

>>> 输出 Kotlin DSL

声明:

fun connect(subscriber: (MqttSubscriber.() -> Unit)? = null) {

    val callback = MqttSubscriber()
    if (subscriber != null) {
        callback.subscriber()
    }
}

调用:

clipboard.png

大白话解释:调用函数类型的时候,传了个对象进来,顺便修改了函数类型的上下文对象。


再来个例子:

// 这是 HTML 最外层的标签: <html>
class HTML : BaseElement("html") {

    
    fun common() {
        //原始写法
        val body = Body().apply{
            //do something
        }
        this.children += body
    }
    
    //把上面的原始写法封装一下
    fun body(block: Body.() -> Unit): Body {
        val body = Body()
        body.block()
        this.children += body
        return body
    }
}

用带接收者的高阶函数封装行为
makeAnimator(leftMargin, viewSize, width) {
    addUpdateListener { v ->
        layoutAttrs.leftMargin = v.animatedValue as Int
        updateViewLayout(tag, false)
    }
    addListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator?) {
            endMoveAndRecord()
        }
    })
}
private inline fun makeAnimator(from: Int, size: Int, containerSize: Int, setup: ValueAnimator.() -> Unit) {
    if (size <= 0 || containerSize <= 0) return
    ValueAnimator.ofInt(from, if (from <= (containerSize - size) / 2) 0 else (containerSize - size))
        .apply {
            duration = 150L
            setup()
        }
        .start()
}

16. 协程

优势:

  • 方便的线程切换
  • 像写同步代码一样写异步代码
CoroutineScope(Dispatchers.Main).launch {
            
            //主线程执行代码
            //.. 
            
            val bitmap = withContext(Dispatchers.IO){ //切到子线程
                // ...
                getImage(url) //最后一行可作为返回值返回
            }
            
            //自动切回主线程
            //.. 
        }

17.位运算符

clipboard.png

18. List 遍历的几种方式

遍历索引

for (i in list.indices)

遍历item,相当于 foreach

for (i in list)

又遍历索引,又遍历item

for ((index,element) in list.withIndex)

19. 函数类型

第一种: 使用双冒号::

实参: ::callback
形参: (参数1, 参数2)->返回值

本质上还是传递了对象,自动生成 Function 对象,在对象中调用的回调函数。

注意: :: 只能用来将函数作为实参去传递

class Test {

    fun main() {
        Test2().method("aaa", this::callback)
    }

    fun callback(arg: String) {
        Log.e("zkt", "测试双冒号传递函数 -> $arg")
    }

}
class Test2 {
    
    fun method(arg1: String, callback: (String) -> Unit) {
        callback("我是回调函数")
    }
}

第二种:传递匿名函数

launch("aaa", fun(){})

//也可以省略fun()

launch("aaa", {})
private fun launch(aaa : String, request: () -> Unit) {

}

20. 注解

@JvmStatic

生成真正的 static,而不是kotlin编译后自动创建的 Companion 对象

companion object {

    @JvmStatic
    fun newInstance(param1: String, param2: String) {}
}

@JvmField

不会自动生成getter/setter方法

@JvmField var title: String = ""

@Volatile

@Volatile会将JVM备份字段标记为volatile。

@Volatile private var running = false

@JvmOverloads

重写java的构造器时需要此注解。比如继承View

class TextComponent @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
    : LinearLayout(context, attrs, defStyleAttr) {

21. lateinit 和 by lazy 延迟初始化

lateinit 用于修饰var变量,它会让编译器暂时忽略初始化这个事情,到后面用的时候我们在进行初始化,但是不能用到基本数据类型,比如int,double这种。

lateinit var test: String

by lazy用于val类型的变量,它会暂时不进行初始化,并且在第一次使用的时候自动调用我们设置好的表达式进行初始化。

val str by lazy {
        println("Init lazy")
        "Hello World"
    }

22. 构造函数

如果主构造函数和次构造函数同时存在的时候,次构造函数必须调用主构造函数

class Student(username: String) {
    private var username: String
    private var age: Int

    init {
        this.username = username
        this.age = 10
    }

    constructor(username: String, age: Int) : this(username) {
        this.age = age
    }
}

23. 扩展函数 和 扩展属性

扩展函数,其实就是扩展类的函数,可以在已有的类中添加新的方法,比继承更加简洁优雅方便。

fun Activity.showToast( msgId:Int){
    Toast.makeText(this,msgId,Toast.LENGTH_SHORT).show()
}

这样任何的Activity里面就可以直接调用showToast方法来展示Toast了。

同样,可以设置 扩展属性,比如:

var <T> MutableList<T>.lastData: T
    //获取List中最后一个对象
    get() = this[this.size - 1]
    //设置List中最后一个对象的值
    set(value) {
        this[this.size - 1] = value
    }

用法:

var strs = mutableListOf<String>()
strs.lastData="heihei"
Log.e(TAG,"lastdata= ${strs.lastData}")

24. 单例

object 关键字在java中:

  • public static final class
  • 构造函数为 private
  • 声明的变量都是 static final 修饰的静态常量
class MqttManager {
    companion object {
        fun getInstance(): MqttManager {
            return Holder.mInstance
        }
    }
    
    object Holder {
        val mInstance = MqttManager()
    }
}
companion object {
    private lateinit var sInstance: NetworkWatchLiveData
    @MainThread
    fun get(): NetworkWatchLiveData {
        sInstance = if (::sInstance.isInitialized) sInstance else NetworkWatchLiveData()
        return sInstance
    }
}

静态内部类方式

open class OffsetHelper {

    companion object {
       
        val instance = SingletonHolder.holder
        
        @Volatile
        private var mainThreadHandler: Handler? = null
    }
    
    private constructor() {
        mainThreadHandler = Handler(Looper.getMainLooper())
    }

    /**
     * 静态内部类单例实现
     */
    private object SingletonHolder {
        val holder = OffsetHelper()
    }

}

java实现:

public static final class Holder {
  @NotNull
  private static final MqttManager mInstance;
  public static final MqttManager.Holder INSTANCE;

  @NotNull
  public final MqttManager getMInstance() {
     return mInstance;
  }

  private Holder() {
  }

  static {
     MqttManager.Holder var0 = new MqttManager.Holder();
     INSTANCE = var0;
     mInstance = new MqttManager();
  }
}

全局 LiveData

class GlobalLivedata : LiveData<String>() {

    companion object {

        private lateinit var globalData: GlobalLivedata
        fun getInstance(): GlobalLivedata {
            globalData = if (::globalData.isInitialized) globalData else GlobalLivedata()
            return globalData
        }
    }
}

25. reified 让泛型更简单安全

众所周知,java的泛型擦除,在运行时任何泛型都是Object,所以泛型无法当做具体的类型来处理。

但是kotlin中可以通过 reified 关键字使之在运行时也能确定具体的类型。

reified 必须和 inline 一块使用,可以让泛型作为一个运行时能解析的具体的类型。

示例代码

inline fun <reified T : Activity> Activity.startActivity() {
    startActivity(Intent(this, T::class.java))
}

可以看到,可以把泛型当做一个具体的类型使用。
调用时也很简单:

startActivity<MainActivity>()

原理
泛型不作为泛型了,而是作为了参数传递了

public final class ActivityExKt {
   // $FF: synthetic method
   public static final void startActivity(Activity $this$startActivity) {
      int $i$f$startActivity = 0;
      Intrinsics.checkNotNullParameter($this$startActivity, "$this$startActivity");
      Context var10003 = (Context)$this$startActivity;
      Intrinsics.reifiedOperationMarker(4, "T");
      $this$startActivity.startActivity(new Intent(var10003, Activity.class));
   }
}

26. inline 内联函数

inline、noinline、crossinline傻傻分不清楚

27. 创建只有静态方法的工具类

创建一个无class代码块的纯kt文件,decompile后会发现所有的方法都是public static final修饰的,都是类属性、类方法。

和object类要区分,object修饰的class是单例,说白了还是通过对象去调用的方法。

28. 集合函数式API

Kotlin开发五 Kotlin中集合常用函数式API 函数式编程的魔法武器:Kotlin高阶函数和Lambda表达式

  • map

    map函数是最常用的一种函数式API,它用于将集合中的每个元素都映射成一个另外的值,映射的规则则在lambda表达式中指定,最终形成一个新的集合 fun main() { val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon") val newList = list.map { it.toUpperCase() } for (fruit in newList){ print("${fruit},") } } //APPLE BANANA ORANGE PEAR GRAPE WATERMELON

  • filter 过滤数据

    filter函数是用来过滤集合中数据的,内部实现是遍历集合并把符合条件的放进新的集合中,它可以单独使用,也可以配合map函数一起使用,比如我们想保留5个字母以内的水果: fun main() { val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon") val newList = list.filter { it.length <= 5 }.map { it.toUpperCase() } for (fruit in newList){ print("${fruit} ") } } //APPLE PEAR GRAPE

  • find 找到第一个满足条件的

    find找到集合中第一个满足条件的元素,返回值是集合元素类型,可能为null @kotlin.internal.InlineOnly public inline fun Iterable.find(predicate: (T) -> Boolean): T? { return firstOrNull(predicate) }

    public inline fun <T> Iterable<T>.firstOrNull(predicate: (T) -> Boolean): T? {
        for (element in this) if (predicate(element)) return element
        return null
    }
    
    //测试find
    val find = list.find { it.age > 25 }
    println(find)
    
    输出:
    User(id=3, name='王五', age=65, sex='男')
    
  • all 所有元素满足指定条件

    • all函数用于判断集合中是否所有元素都满足指定条件
    fun main() {
        val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
        val anyResult = list.any { it.length <= 5 }
        val allResult = list.all { it.length <= 5 }
        println("anyResult is ${anyResult}, allResult is ${allResult}")
    }
    //anyResult is true, allResult is false
    
  • any 至少存在一个元素满足指定条件

    用于判断集合中是否至少存在一个元素满足指定条件

    内部实现是遍历list,每遍历一项都会判断,如果符合就 return true 退出循环;否则一直遍历,最终都没匹配到就 return false

    等同于「for循环」里面写了个「if」语句

    var bool = list.any {
        it.isSelected && it.name != "你好"
    }
    
  • 过滤操作符汇总

    • drop():从第一项开始去除前n个元素,并返回剩余的元素列表。
    • dropWhile{}:根据给定函数从第一项开始去掉指定元素,直到不满足条件为止的前面的元素,并返回剩余元素列表。
    • dropLastWhile{}:根据给定函数从最后一项开始去掉指定元素,直到不满足条件为止的后面的元素,并返回剩余元素的列表。
    • filter{}:过滤出所有符合给定函数条件的元素。
    • filterNot{}:过滤所有不符合给定函数条件的元素。
    • filterNotNull():过滤所有元素中不是null的元素。
    • slice():过滤一个list中指定index的元素。
    • take():返回从第一个开始的n个元素。
    • takeLast():返回从最后一个开始的n个元素。
    • takeWhile{}:返回从第一个开始符合给定函数条件的元素,直到不符合条件为止。
    • takeLastWhile{}:返回从最后一个开始符合给定函数条件的元素,直到不符合条件为止。
  • reduce 累计,无初始值,将当前的结束值作为下次的开始值

    累计计算,这次拿到的值是上次的计算结果

    val numbers = listOf(1, 2, 3, 4, 5)
    val sum = numbers.reduce { acc, i -> acc + i }
    println(sum) // 输出 15
    
  • fold 累计,有初始值,将当前的结束值作为下次的开始值。

    与 reduce 函数类似,但是可以指定一个初始值

    val numbers = listOf(1, 2, 3, 4, 5)
    val sum = numbers.fold(0) { acc, i -> acc + i }
    println(sum) // 输出 15
    

29. joinToString 集合转字符串

内部实现是创建了一个 StringBuilder,遍历集合并按照分隔符拼接每个item

val newStr = list.joinToString(",") { item ->
        item.serviceType
}

30. repeat 循环的封装

循环执行n次block中的代码

repeat(3){
    println("repeat")
}

31. 委托/代理

Kotlin“委托类”委托的是接口方法,而“委托属性”委托的,则是属性的 getter、setter

属性委托

第一种:Delegates.observable()

主要用于监控属性值发生变更,观察者模式,当属性值被修改后会往外部抛出一个变更的回调。

它需要传入两个参数,一个是 initValue 初始化的值,另一个就是回调 lamba, 回调出 property, oldValue, newValue 三个参数。

实战一:

import kotlin.properties.Delegates

class Person{
    var address: String by Delegates.observable(initialValue = "NanJing", onChange = {property, oldValue, newValue ->
        println("property: ${property.name}  oldValue: $oldValue  newValue: $newValue")
    })
}

fun main(args: Array<String>) {
    val person = Person().apply { address = "ShangHai" }
    person.address = "BeiJing"
    person.address = "ShenZhen"
    person.address = "GuangZhou"
}

实战二:

class MainActivity : BaseActivity() {
    private var backPressedTime by Delegates.observable(0L) { pre, old, new ->
        /**
         * 2 次的时间间隔小于2秒就退出了
         */
        if (new - old < 2000) {
            finish()
        } else {
            drawerLayout?.snack("再按返回鍵退出")
        }
    }

    /**
     * 从新写back方法
     */
    override fun onBackPressed() {
        /**
         * 直接赋值就可以啦,是不是很简单呀
         */
        backPressedTime = System.currentTimeMillis()
    }
}

第二种:Delegates.vetoable()

主要用于监控属性值发生变更,观察者模式,当属性值被修改后会往外部抛出一个变更的回调。

它需要传入两个参数,一个是initValue初始化的值,另一个就是回调lamba, 回调出property, oldValue, newValue三个参数。

与observable不同的是这个回调会返回一个Boolean值,来决定此次属性值是否执行修改。

import kotlin.properties.Delegates

class Person{
    var address: String by Delegates.vetoable(initialValue = "NanJing", onChange = {property, oldValue, newValue ->
        println("property: ${property.name}  oldValue: $oldValue  newValue: $newValue")
        return@vetoable newValue == "BeiJing"
    })
}

fun main(args: Array<String>) {
    val person = Person().apply { address = "NanJing" }
    person.address = "BeiJing"
    person.address = "ShangHai"
    person.address = "GuangZhou"
    println("address is ${person.address}")
}

第三种:Delegates.notNull()

和 lateinit 功能类似,可以不在构造器初始化时候初始化而是可以延迟到之后再初始化这个var 修饰的属性

开发者要做到可控,也就是一定要确保属性初始化是在属性使用之前,否则会抛出一个IllegalStateException。

import kotlin.properties.Delegates

class Teacher {
    var name: String by Delegates.notNull()
}
fun main(args: Array<String>) {
    val teacher = Teacher().apply { name = "Mikyou" }
    println(teacher.name)
}

第四种:自定义属性委托

如果我们需要为 val 属性定义委托,我们就去实现 ReadOnlyProperty 这个接口;如果我们需要为 var 属性定义委托,我们就去实现 ReadWriteProperty 这个接口。这样做的好处是,通过实现接口的方式,IntelliJ 可以帮我们自动生成 override 的 getValue、setValue 方法。

public fun interface ReadOnlyProperty<in T, out V> {
    public operator fun getValue(thisRef: T, property: KProperty<*>): V
}

public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
    public override operator fun getValue(thisRef: T, property: KProperty<*>): V

    public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
}

来看下代码怎么写:


class StringDelegate(private var s: String = "Hello") {
//     ①                           ②                              ③
//     ↓                            ↓                               ↓
    operator fun getValue(thisRef: Owner, property: KProperty<*>): String {
        return s
    }
//      ①                          ②                                     ③ 
//      ↓                           ↓                                      ↓
    operator fun setValue(thisRef: Owner, property: KProperty<*>, value: String) {
            s = value
    }
}

//      ②
//      ↓
class Owner {
//               ③
//               ↓     
    var text: String by StringDelegate()
}

也可以委托已经定义的另一个字段

class Item {
    var count: Int = 0
    //              ①  ②
    //              ↓   ↓
    var total: Int by ::count
}

类委托

只要实现同一个接口就行。

比如将自定义实现了SharePreference接口的类委托给系统的SharePreference实现,接口中所有方法会自动调用委托类中的方法

class Delegate3(context: Context) : SharedPreferences by  context.getSharedPreferences("name", Context.MODE_PRIVATE){
   
}

32. 引用:函数引用/属性引用

java中只有对象才有引用,如果调用基本类型或方法,得到的是值,而在kotlin中有了获取基本类型引用或方法引用的能力

通过属性委托示例可以看到,这里的“::count”是属性的引用,和函数引用是一个概念

属性引用

class Item {
    var count: Int = 0
    //              ①  ②
    //              ↓   ↓
    var total: Int by ::count
}

函数引用

// 函数赋值给变量                    函数引用
//    ↑                              ↑
val function: (Int, Int) -> Float = ::add

33. infix 中缀表达式

try{} catch(Exception e){} 一样的调用格式

34. 计算函数耗时

measureTimeMillis {

}

其内部实现:

public inline fun measureTimeMillis(block: () -> Unit): Long {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    val start = System.currentTimeMillis()
    block()
    return System.currentTimeMillis() - start
}

35. try catch封装

 val result = kotlin.runCatching {

}

//可以根据返回值来判断是否发生了异常
result.isFailure