Kotlin总结

1.kotlin与java比较

/**
 * 1.kotlin与java比较
 * */
private fun kotlinCompareWithJava() {
    //1 打印日志
    println("汤坤,你好!")//system.out.println("汤坤,你好!")
    println("1.打印日志")

    //2 定义常量和变量
    var a = "1"//String a = "1"
    val b = "2" //final String b = "2"
    println("2.定义常量和变量:$a+$b")

    //3 null声明
    var c: String? = null//String c = null
    println("3.null声明:$c")

    //4 空判断
    var text = "123"
    var length: Int = 0
    length = text?.length//实现方式1
    text?.let { length = text.length }//实现方式2 通过let函数
    println("4.空判断:$length")

    //5 字符串拼接
    var localHost = "192.168.0.1"
    var port = "8080"
    var url = "$localHost:$port"//利用美元符
    println("5.字符串拼接:$url")

    //6 三元表达式
    var x = 5
    var message = if (x >= 5)
        "x>=5"
    else
        "x<5"
    println("6 三元表达式:$x")

    //7 多重条件
    if (x in 1..5) {//if(x >= 1 && x <= 5)
        println("利用in函数,实现取值范围,注意范围是前闭后闭:$x")
    }

    //8 switch case语句、if else语句
    var code = 200
    when (code) {
        20, 30 -> println("20,30")
        in 100..199 -> {
            println("重试重定向")
        }
        in 200..299 -> {
            println("成功")
        }
        in 300..399 -> {
            println("重试重定向")
        }
        in 400..499 -> {
            println("参数错误")
        }
        in 500..599 -> {
            println("服务器故障")
        }
        else -> {
            println("出错啦!")
        }
    }

    //9 for循环Java版本代码
    for (int i = 1; i <= 10 ; i++) { }
    for (int i = 1; i < 10 ; i++) { }
    for (int i = 10; i >= 0 ; i--) { }
    for (int i = 1; i <= 10 ; i+=2) { }
    for (int i = 10; i >= 0 ; i-=2) { }
    for (String item : collection) { }
    for (Map.Entry<String, String> entry: map.entrySet()) { }
    //9 for循环 kotlin版本
    for (i in 1..10) {
    }//for (int i = 1; i <= 10 ; i++) { }
    for (i in 1 until 10) {
    }//for (int i = 1; i < 10 ; i++) { }
    for (i in 10 downTo 0) {
    }//for (int i = 10; i >= 0 ; i--) { }
    for (i in 1..10 step 2) {
    }//for (int i = 1; i <= 10 ; i+=2) { }
    for (i in 10 downTo 0 step 2) {
    }//for (int i = 10; i >= 0 ; i-=2) { }
    var list = mutableListOf<String>()
    for (item in list) {
    }//for (String item : collection) { }
    var map = HashMap<Int, String>()
    map[0] = "第一"
    map[1] = "第二"
    for ((key, value) in map) {
        println("打印map中的元素:$key+$value")
    }//for (Map.Entry entry: map.entrySet()) { }

    //10 集合操作,新增元素
    var listNum = listOf(1, 2, 3, 4)//final List listOfNumber = Arrays.asList(1, 2, 3, 4);

    /**
     * java代码,往map中添加元素
     * final Map keyValue = new HashMap();
     * map.put("1", "第一名");
     * map.put("2", "第二名");
     * */
    var mapNum = mapOf("1" to "第一名", "2" to "第二名")//等价于往map集合中添加key、value键值对,"1"和"第一名"、"2"和"第二名"

    //11 遍历
    var listScore = listOf(78, 60, 88, 90)
    //打印集合中元素
    listScore.forEach { println("打印集合中元素:$it") }
    listScore.filter { it > 80 }.forEach {
        println("过滤并打印出集合中元素:$it")
    }

    //12 方法定义
    //void play(String... stop){}
    fun play(vararg stop: String) {//vararg表示可变参数
        println("12 vararg表示可变参数,等价于java中的...,vararg i:Int等价于int... i")
    }

    //13 constructor构造器和静态方法声明
    //TODO 通过构造函数创建对象
    var constructorAndStaticMethod = InnerClassA("tangkun")
    //TODO 通过类直接调用里面的静态方法
    InnerClassA.add(1, 2)

    //14 get、set方法
    var innerClassB = InnerClassB("张三", 50)
    var name = innerClassB.name
    var age = innerClassB.age
    println("get、set方法获取类中的里面的属性:$name:$age")

    //15 操作符
    // & | ^ >> << >>>
    val aa = 2
    val bb = 1
    val ab1 = aa and bb//aa&bb
    val ab2 = aa or bb//aa|bb
    val ab3 = aa xor bb//aa^bb
    val ab4 = aa shr bb//aa>>bb
    val ab5 = aa shl bb//aa<
    val ab6 = aa ushr bb//aa>>>bb
}

2.集合以及集合的处理

/**
 * 2.集合以及集合的处理
 * */
private fun kotlinCollections() {
    //16 换行
    val br = """
        |
        |aa
        |bb
        |cc
        """.trimMargin()
    println("利用符号三个引号和竖线和trimMargin函数换行:$br")

    //17 获取数组中元素的索引,初始化数组,里面有5个元素,分别是0、1、2、3、4,
    var arrayAge = IntArray(5) { it * 1 }
    //方法1
    for (i in arrayAge.indices) {
        println("方法1:打印数组中索引和索引:$i,和对应的值:$arrayAge[i]")
    }
    //方法2
    for ((index, item) in arrayAge.withIndex()) {
        println("方法2:打印数组中索引和索引:$index,和对应的值:$item")
    }
    //方法3
    arrayAge.forEachIndexed { index, item ->
        println("方法3:打印数组中索引和索引:$index,和对应的值:$item")
    }

    //18 集合
    //一、不可变集合
    var notChangeList = listOf(1, 2, 3)
    notChangeList.forEachIndexed { index, i ->
        println("打印不可变集合中元素索引和元素的值:$index=$i")
    }
    //二、可变集合
    var changeList = mutableListOf(1, 2, 3)
    changeList.add(4)
    for ((index, item) in changeList.withIndex()) {
        println("打印可变集合中元素的索引和元素的值:$index=$item")
    }
    //集合排序
    changeList.sort()
    //集合排序复杂版
    var personList = mutableListOf<Person>()
    var person1 = Person("汤坤", 33)
    var person2 = Person("罗丹", 32)
    var person3 = Person("胡磊", 33)
    personList.add(person1)
    personList.add(person2)
    personList.add(person3)

    //TODO 通过对象中的数字属性进行排序
    personList.sortBy {
        it.age
    }
    //TODO 同时还可以通过年龄和名字的首字母同时排序
    personList.sortWith(compareBy({ it.age }, { it.name }))


    //map查询
    var changeMap = mutableMapOf("key1" to 1, "key2" to 2)
    changeMap["key3"] = 3
    if ("key3" in changeMap) {
        println("如果key在map中,打印相应的value:${changeMap["ke3"]}")
    }
    if (3 in changeMap.values) {
        println("3这个value在map集合中")
    }
    if (changeMap.containsKey("key3")) {
        println("如果key在map中,打印相应的value:${changeMap["ke3"]}")
    }
}

3.操作符

/**
 * 3.操作符
 * */
private fun kotlinOperator() {
    //19 Elvis操作符?:,操作符在嵌套属性访问时很有用,起到三元表达式的效果
    var author: String? = "tangkun"
    author = null
    var length2 = author?.length ?: -1

    //20 类型转换
    var typeName = null
    if (typeName is String) {
        println("类型转换is:$typeName")
    } else {
        println("不是String类型")
    }
    //TODO 如果typeName为null会报错,所以我们暂时注释掉
//        var transition = typeName as String
    //如果typeName为null不会报错
    var transitionSafe = typeName as? String
//        println("类型转换as:$transition")
    println("类型转换as?:$transitionSafe")

    //21 init函数,用于初始化代码; init 代码块执行时机在类构造之后,但又在“次构造器”执行之前。
    var innerClassA = InnerClassA("测试init函数")

    //22 继承,类和方法必须通过open关键字修饰,才能够被继承,因为kotlin类和方法默认是final类型
    var classB = ClassB()
    classB.paly()

    //23 this关键字 在外部类和内部类中的使用,内部类需要以Inner修饰
    var innerClass = InnerClass()

    //24 扩展 Extentions,通过扩展,在不修改类源码的情况下,增加方法和属性
    toast("扩展方法,在不修改类的源码情况下实现!")

    //25 委托 Delegation by关键字
    var innerClassA2 = InnerClassA("委托by lazy实现懒加载")
    innerClassA2.main()
}

4.避坑

/**
 * 4.避坑
 * */
private fun kotlinRefuge() {
    //26 常量参数不可修改
    fun play(number: Int = 100) {
        //TODO 定义成常量是无法再次赋值的
//            number = 10
        println("定义成常量是无法再次赋值的:$number")
    }
    play()

    //27 静态变量和方法的两种实现方式:companion和JVMFied、JVMStatic
    var name = CompanionAndJVM.Companion.NAME //TODO 目前的kotlin版本Companion可以省略,但是在java代码中是需要添加Companion的哦
    var name2 = CompanionAndJVM.NAME2//TODO 由于属性添加了JVMField注解,所以在java代码中使用时,无需添加Companion
    var addValue = CompanionAndJVM.Companion.add(1, 2) //TODO 目前的kotlin版本Companion可以省略,但是在java代码中是需要添加Companion的哦
    var addValue2 = CompanionAndJVM.add2(1, 2)//TODO 由于add2方法添加了JVMStatic注解,所以在java中调用该方法,不再需要添加Companion
    println("打印静态变量和静态方法:$name:$addValue:$addValue2")
    
	//Java版本调用代码如下
	//TODO 通过对kotlin中带有常量参数的静态方法上,添加如下注解@JvmOverloads,实现在java代码中调用kotlin里的静态方法不用传递常量参数
	TestKotlinActivity.CompanionAndJVM.Companion.testForJavaConst();
	TestKotlinActivity.CompanionAndJVM.Companion.add(1,2);//java调用kotlin静态方法需要添加Companion
	TestKotlinActivity.CompanionAndJVM.add2(1,2);//TODO add2方法添加了JVMStatic注解,所以不需要Companion
	TestKotlinActivity.CompanionAndJVM.Companion.getNAME();//java调用kotlin静态属性需要添加Companion
	String name = TestKotlinActivity.CompanionAndJVM.NAME2;//TODO NAME添加了JVMField注解,所以不需要Companion

    //28 java中使用kotlin下的带有常量的方法,并且不传递参数
    println("通过对kotlin中带有常量参数的静态方法上,添加如下注解@JvmOverloads,实现在java代码中调用kotlin里的静态方法不用传递常量参数")

    //29 非空判断
    var message: String? = null
    fun messageCheck() {
        if (message != null) {
            var messageLength = message.length
            println("非空判断$messageLength")
        } else {
            println("message为null")
        }
    }
    messageCheck()

    //30 kotlin重写java类的方法时,参数尽量添加为空符号,否则容易出现参数为null情况的错误
    println("kotlin重写java类的方法时,参数尽量添加为空符号,否则容易出现参数为null情况的错误")

    //31 also关键字
    var textString = "111"
    var textLength: Int
    if (textString.length > 0.also { textLength = it }) {//等价于 if(textString.length > 0){ textLength = textString.length}
        println("also关键字的使用:$textLength")
    }

    //31 takeIf关键字
    textString?.takeIf { textLength > 0 }.apply {//等价于 if(textString != null && textLenth > 0){}
        println("takeIf关键字的使用:同java中&&")
    }

    //32 单例模式,双重检验锁实现方式
    Singleton.Companion.instance
}

5.kotlin高阶函数

/**
 * 5.kotlin高阶函数
 *
 * 高阶函数有多重要?
 *
 * 高阶函数,在 Kotlin 里有着举足轻重的地位。
 * 它是 Kotlin 函数式编程的基石,它是各种框架的关键元素,
 * 比 如:协程,Jetpack Compose,Gradle Kotlin DSL。高阶函数掌握好了,会让我们在读源码的时候“如虎添 翼”。
 * */
private fun kotlinGaoJieHanShu() {
    //TODO 33 函数类型 = 参数类型 + 返回值类型 = (Int,Int)->String
    // 比如:(View) -> Unit表示参数类型是View,返回值类型是Unit(相当于java中的void)的函数类型
    fun add(a: Int, b: Int): String {
        return (a + b).toString()
    }

    //TODO 34 高阶函数 高阶函数是将函数用作参数或者返回值参数
    fun add2() {//怎么将函数作为参数呢
        //TODO 将函数作为返回值
        add(1, 2)
    }

    //TODO 35 lambda表达式 用于简化代码,但是有时候感觉代码可读性变差了,
    // 因为很多东西被省略了,要改一个省略的变量就会变得很麻烦
    //TODO it使用场景 当 Kotlin Lambda 表达式只有一个参数的时候,它可以被写成 it。
    tv_test_kotlin.setOnClickListener { it.isClickable.apply { println("被点击了,这里的it就表示OnClickListener的一个实例") } }

    // 36 TODO lambda表达式的8中写法
    //写法1   object的使用
    tv_test_kotlin.setOnClickListener(object : View.OnClickListener {
        override fun onClick(v: View?) {
            println("lambda表达式的8中写法1:object")
        }
    })
    //写法2    删掉object关键字
    tv_test_kotlin.setOnClickListener(View.OnClickListener { view: View? ->
        println("lambda表达式的8中写法2:删除object,同时会自动删除实现方法")
    })
    //写法3   删除SAM(只有一个抽象方法的接口)构造器
    tv_test_kotlin.setOnClickListener({ view: View? ->
        println("lambda表达式的8中写法3:删除SAM(只有一个抽象方法的接口)构造器")
    })
    //写法4 View?可以省略,因为kotlin支持类型推导
    tv_test_kotlin.setOnClickListener({ view ->
        println("lambda表达式的8中写法4:View?可以省略,因为kotlin支持类型推导")
    })
    //写法5 当lambda表达式中只有一个参数时,可以用it代替
    tv_test_kotlin.setOnClickListener({ it ->
        println("lambda表达式的8中写法5:当lambda表达式中只有一个参数时,可以用it代替")
    })
    //写法6  lambda表达式中的it是可以省略的
    tv_test_kotlin.setOnClickListener({
        println("lambda表达式的8中写法6:lambda表达式中的it是可以省略的")
    })
    //写法7 当lambda作为函数的最后一个参数时,lambda可以被挪到外面
    tv_test_kotlin.setOnClickListener() {
        println("lambda表达式的8中写法7:当lambda表达式作为最后一个参数时,lambda表达式可以被挪到外面")
    }
    //写法8 当kotlin只有一个lambda作为函数的参数时,()括号可以被省略
    tv_test_kotlin.setOnClickListener {
        println("lambda表达式的8中写法8:当kotlin只有一个lambda作为函数的参数时,()括号可以被省略")
    }

    //37 泛型
    //一、泛型类定义和使用
    class MyGenerics<T> {
        //泛型类定义
        //非泛型函数
        fun add(t: T) {

        }

        //泛型函数 Java中叫做泛型方法,但是在kotlin中方法叫做函数,同c/c++一个叫法
        fun <T> remove(t: T) {

        }
    }
    //泛型类使用
    val myGenerics = MyGenerics<Person>()
    var person = Person("汤坤", 33)
    myGenerics.add(person)
    myGenerics.remove<Person>(person)

    //二、不变性(Invariant)
    //学生
    open class Student()

    //女学生
    class FemaleStudent : Student()

    //大学
    class University<T>(val name: String) {
        //往外取,表示招聘
        fun get(): Student {
            return Student()
        }

        fun put(t: T) {

        }
    }

    lateinit var university: University<Student>招聘
    //TODO 注意:这里传递的参数是Student,而不能是FemaleStudent,因为泛型不能像对象那样使用继承
//        university = University("女子大学")//这样用编译器会报错
    university = University<Student>("某大学")
    var student = university.get()
    //TODO 注意:这里既可以放Student作为参数,又可以放其子类FemaleStudent作为参数
    university.put(student)

    lateinit var femaleUniversity: University<FemaleStudent>
    femaleUniversity = University<FemaleStudent>("女子大学")
    var femaleStudent = FemaleStudent()
    //TODO 注意:这里不能用student作为参数,而只能用FemaleStudent作为参数,因为泛型类声明做了限制
    femaleUniversity.put(femaleStudent)


    //三、泛型的协变(Covariant) out关键字 表示往外取 (TODO 声明处型变)
    class CovariantUniversity<out T>(var name: String) {
        fun get(): Student {
            return Student()
        }
    }
    //TODO 通过out关键字,并且删除了泛型类中的put传入泛型参数方法,就实现了泛型的继承效果
    var covariantUniversity: CovariantUniversity<Student> = CovariantUniversity<FemaleStudent>("女子大学")
    var covariantStudent = covariantUniversity.get()

    //四、泛型的逆变(Contravariant) in关键字 表示往里存 (TODO 声明处型变)
    class ContravariantUniversity<in T>(var name: String) {
        //TODO 泛型的逆变,表示只能往里面添加数据,而不能往里面取数据
        fun put(t: T) {

        }
    }
    //TODO 这里定义反过来了,前面的泛型参数是子类,后面的泛型参数是父类
    var contravariant: ContravariantUniversity<FemaleStudent> = ContravariantUniversity<Student>("普通大学")
    //注意,这里只能传递参数FemaleStudent,而不能传递参数Student,因为声明的时候做了限制
//        var student4 = Student()
//        contravariant.put(student4)
    contravariant.put(femaleStudent)

    //五、前面两种方式,协变和逆变,都是声明处型变,假如类的声明不可更改,比如:这是一个第三方提供的泛型类,我们无法更改其源码
    //那么就需要使用使用处型变来解决这个问题 TODO 使用处型变,kotlin中使用处型变叫做类型投影(Type Projections)
    //使用处协变
    fun covariantInUse(university: University<out Student>) {
        var femaleStudent: Student? = university.get()
    }

    //使用处逆变
    fun contravariantInUse(university: University<in Student>) {
        var femaleStudent = FemaleStudent()
        university.put(femaleStudent)
    }

    //Kotlin中使用处型变与Java使用处型变比较
    // kotlin out University协变 / in University 逆变
    // java University协变  /  University逆变

    // Java 协变写法
//        University covariant = new University("女子大学");
//        Student student = covariant.get();
//        // 报错
//        covariant.put(student);
//        // Java 这辣鸡逆变语法
//        University contravariant = new University("普通大学");
//        contravariant.put(new FemaleStudent())
//        // 报错
//        Student s = contravariant.get();

    //六、泛型实战
    fun Person.apply(block: Person.() -> Unit): Person {
        block()
        return this
    }

    var person2 = Person("tangkun", 33)
    person2?.apply {
        var name = this.name
        var age = this.age
    }
    //上面的代码使用泛型表示,让所有的类都能用
    fun <T> T.apply(block: T.() -> Unit): T {
        block()
        return this
    }

    //38 扩展函数 Extension 扩展函数和扩展属性
    //一、扩展函数
    //顶层扩展,不能定义在类里面,需要定义在类外面,否则,就是类的属性或函数了
    //顶层扩展的本质就是Java中的静态方法,就好比我们写的Utils工具类中的静态方法

    fun String.log() {
        println(this)
    }

    var extensionTxt = "helloWorld"
    var log = extensionTxt.log()
    println("扩展函数日志打印:$log")

    if (!extensionTxt.isNullOrBlank()) {
        println("扩展函数非空判断isNullOrBlank():$extensionTxt")
    } else {
        println("扩展函数非空判断isNullOrBlank():null")
    }
    //类内扩展
    class InnerExtension(val personNew: Person) {
        fun Person.isOld(): Boolean {
            return this.age > 55
        }

        fun main() {
            var isOld = Person("tangkun", 33).isOld()
            println("类内函数:$isOld")
        }
    }

    var personNew = Person("tangkun", 33)
    var innerExtension = InnerExtension(personNew)
    innerExtension.main()

    //扩展函数的类型是什么?
    //从外表来看,带接受者的函数类型,就等价于成员函数,也就是等价于扩展函数。
    //但从本质上讲,他仍是通过编译器注入this来实现的。
    //举例:
    //A.(B,C)->D?   解释:参数类型是B和C,返回值类型是D,接受者是A
    // fun A.textExt(b:B,c:C):D?{}  解释:等价于A类的扩展函数
    fun testFunctionType(){
        var lambda:A.(B,C)->D? = A::test
        lambda = A::textExt
        lambda = ::textReceiver
        var lambdaX:(A,B,C)->D? = lambda
    }

    //Java版本
    SharedPreferences sharedPreferences= getSharedPreferences("data",Context.MODE_PRIVATE);
    SharedPreferences.Editor editor = sharedPreferences.edit();
    editor.putString(SP_KEY_RESPONSE, response);
    editor.commit(); editor.apply();

    //kotlin版本 扩展函数,感觉这种写法还没有理解到
    fun SharedPreferences.edit(commit: Boolean = false, action: SharedPreferences.Editor.() -> Unit) {
        val editor = edit()
        action(editor)
        if (commit) {
            editor.commit()
        } else {
            editor.apply()
        }
    }
    //使用
    val sharedPreferences: SharedPreferences by lazy(LazyThreadSafetyMode.NONE) {
        getSharedPreferences("SP", MODE_PRIVATE)
    }

    fun diaplay(content: String?) {
        sharedPreferences.edit {
            putString("key_content", content)
        }
    }

    /**
     * 借助扩展函数,实现方便的 Api */
    fun ktxSpan(s: CharSequence = SpannableString(""), func: CharSequence.() -> SpannableString) = func(s)
    fun CharSequence.bold(s: CharSequence = this) = span(s, StyleSpan(Typeface.BOLD))
    fun CharSequence.italic(s: CharSequence = this) = span(s, StyleSpan(android.graphics.Typeface.ITALIC))
    fun CharSequence.underline(s: CharSequence = this) = span(s, UnderlineSpan())
    fun CharSequence.strike(s: CharSequence = this) = span(s, StrikethroughSpan())
    fun CharSequence.url(s: CharSequence = this) = span(s, URLSpan(s.toString()))
    var name = "Jake Wharton"
    var blog = "https://jakewharton.com"
    tv_test_kotlin.text = ktxSpan {
        name?.bold().italic().sizeOf(1.3F).backgroud(Color.YELLOW)
                .append("\n")
                .append("\n")
                .append("Square".strike().italic().sizeOf(0.8F).color(Color.GRAY))
                .append("\n")
                .append("Google".color(Color.BLUE).underline())
                .append("\n")
                .append(url(blog!!, blog))
    }

    //总结:
    //提到 Kotlin,大家最先想起的可能是 扩展 ,其次是 协程 ,再要不就是 空安全 ,委托根本排不上号。
    //但是,在一 些特定场景中,委托的作用是无比犀利的。

    // 39 委托 Delegation by关键字
    // 包含了委托类和委托属性
    // 委托类委托出去的是他的接口实现;而委托属性委托出去的是属性的getter、setter
    //一、委托类
    fun testDelegate() {
        //将委托对象SqlDB或者GreenDaoDB作为参数传入,而
        UniversalDB(SqlDB()).save()
        UniversalDB(GreenDaoDB()).save()
    }
    testDelegate()

    //二、委托属性
    val delegateProperty: String? by lazy {
        println("委托by lazy中的代码只会执行一次")
        "委托属性:by lazy"
    }
    println("委托by lazy使用:$delegateProperty")

    //自定义委托
    class CustomDelegate(private var s: String = "Hello World") {
        //any对应message所处的类   返回值String对应message的函数类型
        operator fun getValue(any: Any, property: KProperty<*>): String {
            return s
        }

        //any对应message所处的类 参数value的类型String对应message的函数类型
        operator fun setValue(any: Any, property: KProperty<*>, value: String) {
            s = value
        }
    }


    class Owner {
        var message: String by CustomDelegate()
    }


    //小结:
    //var ——我们需要提供getValue和setValue
    //val —— 则只需要 getValue
    //operator —— 是必须的,这是编译器识别委托属性的关键。注释中已用 ⚡ 标注了。
    //property —— 它的类型一般固定写成 KProperty<*>
    //value —— 的类型必须是委托属性的类型,或者是它的父类。也就是说例子中的 value: String 也可以
    //换成 value: Any 。注释中已用 ↓ 标注了。
    //thisRef ——它的类型,必须是属性所有者的类型,或者是它的父类。也就是说例子中的thisRef:
    //Owner 也可以换成 thisRef: Any 。注释中已用  标注了


    //委托实战
    class IMG : BaseElement("img") {
        var src: String
            get() = hashMap["src"]!!
            set(value) {
                hashMap["src"] = value
            }

        // ↑
        // 看看这重复的模板代码 // ↓
        var alt: String
            get() = hashMap["alt"]!!
            set(value) {
                hashMap["alt"] = value
            }
    }

    //todo 委托实现方式
    // 这代码看着真舒服
    class IMG : BaseElement("img") {
        var src: String by hashMap
        var alt: String by hashMap
    }

    //按照前面讲的自定义委托属性的要求,我们很容易就能写出这样的代码:// 对应 IMG 类
    // 
    operator fun HashMap<String, String?>.getValue(thisRef: Any, property: KProperty<*>): String? =
            get(property.name)
    operator fun HashMap<String, String>.setValue(thisRef: Any, property: KProperty<*>, value:
    String) =
            put(property.name, value)

}

6.Kotlin协程

/**
 * 6.Kotlin协程
 * 协程,可以理解为轻量级的线程。协程跟线程的关系,有点像“线程与进程的关系”。
 * */
private suspend fun kotlinCoroutines() {
    // 小结
    // 一个线程可以对应多个协程
    // 协程将线程划分成更小的单元
    // 协程跟线程的关系,有点像“线程与进程的关系”。

    //1.协程log调试,查看协程数量,协程的挂起和运行状态,以及每一个协程的日志打印
    fun main() {
        runBlocking<Unit> {
            fun log(msg: Any) {
                println("${Thread.currentThread().name} msg=$msg")
            }
            log(1)
            launch {
                val a = 4
                delay(300)
                log(a)
            }
            launch {
                val b = 3
                delay(200)
                log(b)
            }
            launch {
                val c = 2
                delay(100)
                log(c)
            }
        }
    }
    main()

    //        TODO 线程与协程的比较

    //        线程特点
    //        线程是操作系统级别的概念
    //        我们开发者通过编程语言(Thread.java)创建的线程,本质还是操作系统内核线程的映射
    //        JVM 中的线程与内核线程的存在映射关系,有“一对一”,“一对多”,“M对N”。JVM 在不同操作系统中的具体 实现会有差别,“一对一”是主流
    //        一般情况下,我们说的线程,都是内核线程,线程之间的切换,调度,都由操作系统负责
    //        线程也会消耗操作系统资源,但比进程轻量得多
    //        线程,是抢占式的,它们之间能共享内存资源,进程不行
    //        线程共享资源导致了 多线程同步问题
    //        有的编程语言会自己实现一套线程库,从而能在一个内核线程中实现多线程效果,早期 JVM 的“绿色线程” 就是这么做的,这种线程被称为“用户线程”

    //        协程特点
    //        Kotlin 协程,不是操作系统级别的概念,无需操作系统支持
    //        Kotlin 协程,有点像上面提到的“绿色线程”,一个线程上可以运行成千上万个协程
    //        Kotlin 协程,是用户态的(userlevel),内核对协程无感知
    //        Kotlin 协程,是协作式的,由开发者管理,不需要操作系统进行调度和切换,也没有抢占式的消耗,因此 它更加高效
    //        Kotlin 协程,它底层基于状态机实现,多协程之间共用一个实例,资源开销极小,因此它更加轻量
    //        Kotlin 协程,本质还是运行于线程之上,它通过协程调度器,可以运行到不同的线程上

    //        总结:
    //        一个线程可以包含成千上万个协程,协程不能单独存在,必须依附于线程,但是一个协程可以在不同的线程之间切换
    //        kotlin协程的核心竞争力体现在,他能简化异步并发任务。

    //2.协程的要点:suspend 挂起函数,有挂起和暂停的意思,能被挂起,肯定也能恢复,一般是成对出现
    //suspend的本质,就是CallBack,反编译成java代码叫做Continuation
    suspend fun getUserInfo(): String {
        //Dispatchers.IO表示协程中的子线程
        withContext(Dispatchers.IO) {
            delay(1000)
        }
        return "tangkun"
    }

    suspend fun getFriendList(user: String): String {
        withContext(Dispatchers.IO) {
            delay(1000)
        }
        return "HulLei,ZhouJinJie"
    }

    suspend fun getFeedList(friendList: String): String {
        withContext(Dispatchers.IO) {
            delay(1000)
        }
        return "{FeedList..}"
    }
    //注意,这是一个链式调度,在java中是一个3层的嵌套操作,通过获取到用户信息,然后根据用户信息获取到好友列表,然后根据获取到的好友列表获取到好友的动态
    //但是通过协程处理后,就像是一个同步请求一样
    var user = getUserInfo()
    println(user)
    var friendList = getFriendList(user)
    println(friendList)
    var feedList = getFeedList(friendList)
    println(feedList)

//        通过上面的代码,我们能知道:
//        表面上看起来是同步的代码,实际上也涉及到了线程切换。
//        一行代码,切换了两个线程。
//        = 左边:主线程
//        = 右边:IO线程
//        每一次从 主线程 到 IO线程 ,都是一次协程 挂起 (suspend)
//        每一次从 IO线程 到 主线程 ,都是一次协程 恢复 (resume)。
//        挂起和恢复,这是挂起函数特有的能力,普通函数是不具备
//        如果以上代码运行在 Android 系统,我们的 App 是仍然可以响应用户的操作的,主线程并不繁忙,这也很 容易理解。


    //这是一个挂起函数,但是他跟一般的挂起函数有个区别:他在执行的时候,并不会被挂起,因为他就是普通的函数。
    suspend fun getPassword(): String {
        return "123445"
    }
    getPassword()

//        小结
//        suspend 修饰的函数就是 挂起函数
//        挂起函数,在执行的时候并不一定都会挂起
//        挂起函数只能在其他挂起函数中被调用
//        挂起函数里包含其他挂起函数的时候,它才会真正被挂起


    //3.协程 字节码反编译
    // 协程的本质,可以说就是 CPS(Continuation-Pass Style Transformation) + 状态机
    suspend fun testCoroutine() {
        println("start")
        val user = getUserInfo()
        println(user)
        val friendList = getFriendList(user)
        println(friendList)
        val feedList = getFeedList(friendList)
        println(feedList)
    }
    //几个函数,反编译后的结果如下
    fun getUserInfo(completion: Continuation<Any?>): Any?{}
    fun getFriendList(user: String, completion: Continuation<Any?>): Any?{}
    fun getFeedList(friendList: String, completion: Continuation<Any?>): Any?{}

    //接下来我们分析 testCoroutine() 的函数体,因为它相当复杂,涉及到三个挂起函数的调用。
    //首先,在 testCoroutine 函数里,会多出一个 ContinuationImpl 的子类,它的是整个协程挂起函数的核心。代 码里的注释非常详细,请仔细看。
    fun testCoroutine(completion: Continuation<Any?>): Any? {
        class TestContinuation(completion: Continuation<Any?>?) : ContinuationImpl(completion) {
            // 表示协程状态机当前的状态
            var label: Int = 0

            // 协程返回结果
            var result: Any? = null

            // 用于保存之前协程的计算结果 var mUser: Any? = null
            var mFriendList: Any? = null

            // invokeSuspend 是协程的关键
// 它最终会调用 testCoroutine(this) 开启协程状态机
// 状态机相关代码就是后面的 when 语句
// 协程的本质,可以说就是 CPS + 状态机
            override fun invokeSuspend(_result: Result<Any?>): Any? {
                result = _result
                label = label or Int.Companion.MIN_VALUE
                return testCoroutine(this)
            }
        }
    }

    接下来则是要判断 testCoroutine 是不是初次运行,如果是初次运行,就要创建一个 TestContinuation 的实例 对象。
    fun testCoroutine(completion: Continuation<Any?>): Any? {
        ...
        val continuation = if (completion is TestContinuation) {
            completion
        } else {
            // 作为参数
            // ↓
            TestContinuation(completion)
        }
    }

//        invokeSuspend 最终会调用 testCoroutine,
//        然后走到这个判断语句 如果是初次运行,会创建一个 TestContinuation 对象,completion 作为了参数
//        这相当于用一个新的 Continuation 包装了旧的 Continuation
//        如果不是初次运行,直接将 completion 赋值给 continuation
//        这说明 continuation 在整个运行期间,只会产生一个实例,这能极大的节省内存开销(对比 CallBack)


    //接下来是几个变量的定义,代码里会有详细的注释:
    // 三个变量,对应原函数的三个变量
    lateinit var user: String
    lateinit var friendList: String
    lateinit var feedList: String
    // result 接收协程的运行结果
    var result = continuation.result
    // suspendReturn 接收挂起函数的返回值
    var suspendReturn: Any? = null
    // CoroutineSingletons 是个枚举类
    // COROUTINE_SUSPENDED 代表当前函数被挂起了
    val sFlag = CoroutineSingletons.COROUTINE_SUSPENDED

    //然后就到了我们的状态机的核心逻辑了,具体看注释吧:
    when (continuation.label) {
        0 -> {
            // 检测异常 throwOnFailure(result)
            log("start")
            // 将 label 置为 1,准备进入下一次状态 continuation.label = 1
            // 执行 getUserInfo
            suspendReturn = getUserInfo(continuation)
            // 判断是否挂起
            if (suspendReturn == sFlag) {
                return suspendReturn
            } else {
                result = suspendReturn
                //go to next state
            }
        }
        1 -> {
            throwOnFailure(result)
            // 获取 user 值
            user = result as String log (user)
            // 将协程结果存到 continuation 里 continuation.mUser = user
            // 准备进入下一个状态 continuation.label = 2
            // 执行 getFriendList
            suspendReturn = getFriendList(user, continuation)
            // 判断是否挂起
            if (suspendReturn == sFlag) {
                return suspendReturn
            } else {
                result = suspendReturn
                //go to next state
            }
        }
        2 -> {
            throwOnFailure(result)
            user = continuation.mUser as String
            // 获取 friendList 的值 friendList = result as String log(friendList)
            // 将协程结果存到 continuation 里 continuation.mUser = user continuation.mFriendList = friendList
            // 准备进入下一个状态 continuation.label = 3
            // 执行 getFeedList
            suspendReturn = getFeedList(friendList, continuation)
            // 判断是否挂起
            if (suspendReturn == sFlag) {
                return suspendReturn
            } else {
                result = suspendReturn
                //go to next state
            }
        }
        3 -> {
            throwOnFailure(result)
            user = continuation.mUser as String
            friendList = continuation.mFriendList as String
            feedList = continuation.result as String
            log(feedList)
            loop = false
        }
    }

    //TODO 对上面一段代码的解释说明
//        when 表达式实现了协程状态机
//        continuation.label 是状态流转的关键
//        continuation.label 改变一次 , 就代表协程切换了一次
//        每次协程切换后,都会检查是否发生异常
//        testCoroutine 里的原本的代码 , 被 拆分 到状态机里各个状态中 , 分开执行
//        getUserInfo(continuation),getFriendList(user, continuation),getFeedList(friendList, continuation) 三个函数调用传的同一个 continuation 实例。
//        一个函数如果被挂起了,它的返回值会是: CoroutineSingletons.COROUTINE_SUSPENDED
//        切换协程之前,状态机会把之前的结果以成员变量的方式保存在 continuation 中。

    //TODO 看协程转换成java代码技巧:
    //下面的建议会有助于你看协程真实的字节码: 协程状态机真实的原理是:
    // 通过 label 代码段嵌套,配合 switch 巧妙构造出一个状态机结构,这种逻辑比较复杂,相对难懂一些。
    // (毕竟 Java 的 label 在实际开发中用的很少。)
}

7.kotlin Flow

/**
 * 7.kotlin Flow
 * */
private fun kotlinFlow() {
    //流的简单使用
    suspend fun flow() {
        flow<Int> {
            (0..4).forEach {
                it * 5 //生产者发送数据
            }
        }.collect {
            println("flow流的简单使用,flow+collect实现生产者消费者模式:$it")//消费者处理数据
        }
    }

    //流的操作符 flowOf、asFlow、map、transform、take、toList、toSet、first、reduce、buffer、collectLast、zip、combine
    //1.flowOf 作用:可以将flowOf里的可变参数一一发射
    suspend fun flowOf() {
        flowOf(1, 2, 3, 4).collect {
            println("1.flowOf 作用:可以将flowOf里的可变参数一一发射 $it")
        }
    }

    //2.asFlow 作用:可以将集合转换成flow发射
    suspend fun asFlow() {
        listOf(1, 2, 3, 4).asFlow().collect {
            println("2.asFlow 作用:可以将集合转换成flow发射 $it")
        }
    }

    //3.map 作用:我们可以在map中执行一些过渡操作,比如将生产者中的数据进行处理,然后发射给消费者
    suspend fun map() {
        (1..9).asFlow().map {
            //通过map操作符,对数据进行处理,这里是做一个乘5的操作,然后发射给消费者处理
            it * 5
        }.collect {
            println("3.map 作用:我们可以在map中执行一些过渡操作,比如将生产者中的数据进行处理,然后发射给消费者 $it")
        }
    }

    //TODO map 实现网络请求嵌套操作
    //模拟网络请求,登录返回用户id
    fun login(): Int {
        return 10086
    }

    //模拟网络请求,根据用户id获取好友列表
    fun getFriendList(id: Int): String {
        return "ZhouJinJie,HuLei"
    }

    //map执行网络请求嵌套操作,有点RxJava使用的那种流式操作的味道
    //实现的业务:首先获取用户信息,然后根据用户id获取好友列表,然后返回好友列表中数据
    suspend fun mapInRequest() {
        flow<Int> {
            //模拟网络请求,登录获取用户id
            var userId = login()
            userId
        }.map {
            //模拟网络请求,根据用户id获取好友列表
            var friendList = getFriendList(it)
            friendList
        }.collect {
            //打印获取到的好友列表中数据
            println("map执行网络请求嵌套操作,实现嵌套网络访问,但是是采用了流式访问,也就是像建造者设计模式那种代码模式。 $it")
        }
    }

    //4.transform 转换操作符 作用:主要强调的是类型转换
    suspend fun transform() {
        (1..5).asFlow().transform<Int, String> {
            //将int类型数据转换成String类型数据
            "索引:$it"
        }.collect {
            println("4.transform 转换操作符 作用:主要强调的是类型转换. $it")
        }
    }

    //5.take 限长操作符 作用:可以限制我们要消费的数据的数量
    suspend fun take() {
        //take函数中传入的参数限制了消费者消费的数量,虽然我们有5个数据,但是只会打印出3个数据
        (1..5).asFlow().take(3).collect {
            println("5.take 限长操作符 作用:可以限制我们要消费的数据的数量 $it")
        }
    }

    //6.toList 作用:将消费的数据转化成List集合
    suspend fun toList() {
        //filter起到了过滤的作用,就好比Java中添加了if逻辑判断
        (1..5).asFlow().filter {
            it > 3
        }.toList()
    }

    //7.toSet 作用:同样是将消费的数据转换成集合,Set集合
    suspend fun toSet() {
        (1..5).asFlow().toSet()
    }

    //8.first 作用:获取第一个元素
    suspend fun first() {
        (1..5).asFlow().first()//只返回第一个元素1
    }

    //9.reduce 作用:运算符,减一
    //在reduce的lambda表达式中,可以对当前要消费的值和之前计算的值进行计算,得到新值返回。所有值消费完成后返回最终值
    suspend fun reduce() {
        (1..5).asFlow().reduce { accumulator, value ->
            println("$accumulator:$value")
            //将之前相加得到的值,和当前这个值进行相加运算,相当于是1+2+3+4+5
            accumulator + value
        }
    }

    //10.buffer 作用:buffer可以缓存生产者生成的数据,不会被消费者阻塞
    //说明:
    //生产者生产数据的时候消费者是需要等待的,然后生产者发送完数据后消费者处理数据,期间生产者必须等消费者处理完数据才能继续发射数据
    //这就是一种相互阻塞了,那么有没有一种办法能够让消费者消费数据的时候生产者能继续生成对象呢,还真有buffer就可以
    suspend fun buffer() {
        val startTime = System.currentTimeMillis()
        flow<Int> {
            (1..3).forEach {
                delay(300)
                println("生产者生成数据:$it")
            }
        }.buffer(4)
                .collect {
                    delay(400)
                    println("消费者消费数据:$it")
                    println("时间已经过去了${System.currentTimeMillis() - startTime}ms")
                }
    }
    //打印日志如下:
    //1
    //时间已经过了745
    //2
    //时间已经过了1148
    //3
    //时间已经过了1552

    //结论:
    // 如果我们没有用buffer,那么总时长应该2100ms
    // 使用了buffer总时长是:1552=300+400*3
    // 所以使用buffer的时候生产者可以并发发射数据,不会被消费者阻塞

    //11.conflate 作用:当生产者生产的速度大于消费者消费数据的速度时,消费者只能拿到生产者最新发射的数据
    suspend fun comflate() {
        flow<Int> {
            (1..9).forEach {
                delay(100)
                println("生产者生成数据:$it")
            }
        }.conflate()
                .collect {
                    delay(300)
                    println("消费者消费数据:$it")
                }
    }
    //比如上面这段代码,因为有conflate的存在,输出如下:消费者消费数据:
    //1
    //3
    //6
    //9

    //如果没有conflate存在输出如下:消费者消费数据:
    //1
    //2
    //3
    //4
    //5
    //6
    //7
    //8
    //9

    //总结:
    //两者对比,明显能发现使用conflate的例子替我们忽略了很多无法即时处理的数据

    //12.collectLast
    // 作用:如果生产者数据已经发射过来了,消费者还没有把上一次数据处理完,那么消费者直接停止处理上一条数据,直接处理生产者最新发射来的数据
    suspend fun collectLast() {
        flow<Int> {
            (1..9).forEach {
                delay(100)
                println("生产者生成数据:$it")
            }
        }.collectLast {
            delay(800)
            println("消费者处理数据:$it")
        }
    }
    //打印日志如下:消费者处理数据:
    //9

    //13.zip
    //作用:zip操作符可以把两个流合并成一个流,然后在zip方法中将两个流发射的数据进行处理组合后,继续发射给消费者
    //如果两个流长度不一致,按照较短的流来处理
    suspend fun zip() {
        val flow1 = (1..3).asFlow()
        val flow2 = flowOf("张三", "李四", "王五", "老六")
        flow1.zip(flow2) { a, b ->
            "$a:$b"
        }.collect {
            println(it)
        }
    }
    //打印日志如下:老六没有输出,因为是按照两个流中短的那进行处理的
    //1 : 张三
    //2 : 李四
    //3 : 王五

    //14.combine
    //作用:上一节zip的缺点我们清楚了,就是两个流长度不等的时候,较长的流后面部分无法输出
    //那么combine就是用来解决zip这个缺点的(也很难说是缺点,只是应用场景不同罢了,你姑且可以认为是缺点)
    suspend fun combine() {
        val flow1 = (1..5).asFlow()
        val flow2 = flowOf("李白", "杜甫", "安安安安卓")
        flow1.combine(flow2) { a, b ->
            "$a:$b"
        }.collect {
            println(it)
        }
    }
    //输出日志如下: 这TM什么逻辑,看不明白
    //1 : 李白
    //2 : 李白
    //2 : 杜甫
    //3 : 杜甫
    //3 : 安安安安卓
    //4 : 安安安安卓
    //5 : 安安安安卓

    //实现的效果简单逻辑分析:
    //flow发射1,flow2发射 ”李白“ ,打印:1 : 李白
    //flow发射2,flow2未发射数据  ,打印:2 : 李白
    //flow未发射,flow2发射 ”杜甫“ ,2 : 杜甫
    //flow发射3,flow2未发射 ,打印:3 : 杜甫
    //flow未发射,flow2发射 ”安安安安卓“ ,打印:3 : 安安安安卓
    //flow发射4,flow2发射完成  ,打印:4 : 安安安安卓
    //flow发射5,flow2发射完成  ,打印:5 : 安安安安卓

    //15.展平流
    //flatMapConcat
    //flatMapMerge
    //flatMapLatest

    //16.catch 异常流 作用:用于捕获异常,可以同时捕获生产者和消费者中的异常
    suspend fun catch() {
        flow<Int> {
            (1..3).forEach {
                if (it == 3) {
                    throw NullPointerException("模拟空指针异常")
                }
            }
        }.catch { e ->
            println("通过catch捕获异常,$e")
        }.collect {
            println(it)
        }
    }

    //17.onCompletion 流完成
    //使用onCompletion可以在流完成的时候再发送一个值
    suspend fun onCompletion(){
        flowOf(1..4).onCompletion {
            println("流操作完成")
            //TODO 这里还可以再发射一次数据
        }.collect {
            println("消费者处理数据:$it")
        }
    }
    //打印日志:消费者处理数据:
    //1
    //2
    //3
    //4
    //流操作完成
}

依赖的一些类

    /**
     * 12 定义可变参数的方法
     * */
    fun play(vararg i: Int) {
        //void play(int... i){}
    }

    /**
     * 13 constructor构造器 和 静态方法声明
     * */
    class InnerClassA {
        var id: String? = null

        //25 委托 Delegation by lazy实现懒加载,属性在使用的时候才进行初始化
        val sex: String? by lazy {
            println("第一次使用时候会初始化,下一次再使用无需再初始化")
            "male"
        }

        //通过init函数给属性赋值
        init {
            id = "10086"
            println("init函数")
        }

        constructor(name: String?) {
            println("构造函数")
        }

        fun main() {
            println("自己写的不算main方法,不调用都不执行")
            println(sex)
            println("--------")
            println(sex)
        }

        //constructor表示声明构造函数
        //companion object中表示声明的静态方法
        companion object {
            fun add(a: Int, b: Int): Int {
                return a + b
            }
        }
    }

    /**
     * 14 属性的get、set方法
     * */
    data class InnerClassB(var name: String, var age: Int) {

    }

    /**
     * 18 集合
     * */
    data class Person(var name: String, var age: Int)

    /**
     * 22 继承,类和方法必须通过open关键字修饰,才能够被继承,因为kotlin类和方法默认是final类型
     * 父类
     * */
    open class ClassA {
        open fun paly() {
            println("kotlin中类和方法必须要被open修饰,才能被继承和重新,因为kotlin中类和方法默认是final类型")
        }
    }

    /**
     * 22 继承,类和方法必须通过open关键字修饰,才能够被继承,因为kotlin类和方法默认是final类型
     * 子类
     * */
    class ClassB : ClassA() {
        override fun paly() {
            super.paly()
        }
    }

    /**
     * 24 扩展方法,常量可以在方法调用时可省略
     * */
    private fun Context?.toast(msg: String?, length: Int = Toast.LENGTH_LONG) {
        if (this != null) {
            msg?.let {
                Toast.makeText(this, msg, length).show()
            }
        }
    }

    /**
     * 27 静态变量和方法的两种实现方式:companion和JVMFied、JVMStatic
     * */
    class CompanionAndJVM {
        private fun add3(a: Int, b: Int): Int {
            return a + b
        }

        companion object {
            //TODO 目前的kotlin版本Companion可以省略,但是在java代码中是需要添加Companion的哦
            var NAME: String = "汤坤"

            @JvmField
            var NAME2: String = "汤坤"

            //TODO 目前的kotlin版本Companion可以省略,但是在java代码中是需要添加Companion的哦
            fun add(a: Int, b: Int): Int {
                return a + b
            }

            //TODO 由于add2方法添加了JVMStatic注解,所以在java中调用该方法,不再需要添加Companion
            @JvmStatic
            fun add2(a: Int, b: Int): Int {
                return a + b
            }

            //TODO 在Kotlin类中定义一个带有常量的方法,然后在java代码中调用该方法,并且不传递参数
            @JvmOverloads
            public fun testForJavaConst(name: String = "tangkun") {
                println("在Kotlin类中定义一个带有常量的方法,然后在java代码中调用该方法,并且不传递参数")
            }
        }
    }

    /**
     * 32 单例模式,双重检验锁实现方式
     * */
    class Singleton private constructor() {
        companion object {
            val instance: Singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
                Singleton()
            }
        }
    }

    /**
     * 23 this关键字 在外部类和内部类中的使用,内部类需要以Inner修饰
     * */
    inner class InnerClass {

        fun play() {

        }

        suspend fun test() {
            //TODO 表示调用外部类的方法
            this@TestKotlinActivity.kotlinStudy()
            //TODO 表示调用内部类的方法 省略内部类
            this.play()
            //TODO 表示调用内部类的方法 不省略内部类
            this@InnerClass.play()
        }
    }

    /**
     * 39 委托 by关键字 可以很好的简化代码
     * 包含了委托类和委托属性
     * 委托类委托出去的是他的接口实现;而委托属性委托出去的是属性的getter、setter
     * 一、委托类
     * */
    interface DB {
        fun save()
    }

    class SqlDB() : DB {
        override fun save() {
            println("save to sql")
        }
    }

    class GreenDaoDB() : DB {
        override fun save() {
            println("save to greendao")
        }
    }

    //参数db 通过委托by关键字将接口实现委托给db
    //将委托对象SqlDB或者GreenDaoDB作为参数传入,UniversalDB相当于一个壳
    class UniversalDB(db: DB) : DB by db
    //上面这种写法等价于如下java版本代码,感觉非常简洁;这种java写法属于一种设计模式,好像是装饰器模式还是啥来着
    class UniversalDB implements DB {
        DB db;
        public UniversalDB(DB db) { this.db = db; }
        // 手动重写接口,将 save 委托给 db.save()
        @Override//            ↓
        public void save() { db.save(); }
    }

你可能感兴趣的:(Kotlin,kotlin总结)