《第一行代码》总结 —— Kotlin 教程(三)

文章目录

  • 六、泛型
    • 6.1 泛型类
    • 6.2 泛型方法
    • 6.3 泛型实化
      • 6.3.1 类型擦除机制
      • 6.3.2 泛型实化的应用
    • 6.4 泛型的协变
    • 6.5 泛型的逆变
  • 七、委托
    • 7.1 类委托
    • 7.2 属性委托
  • 八、中缀函数

六、泛型

6.1 泛型类

定义

// 类名中声明泛型 T,在类中即可直接使用此泛型
class MyClass<T> {
    fun method(param: T): T {
        return param
    }
}

// 声明泛型时,可以指定其具体类型的上界
// 比如此例中使用 NumberClass 时,具体类型必须为 Number 类或其子类
class NumberClass<T : Number> {
    fun method(param: T): T {
        return param
    }
}

使用泛型类

// 使用泛型类时,需要指定具体类型
val myClass = MyClass<Int>()
println(myClass.method(666))

6.2 泛型方法

定义和使用都是类似的

class MyClass {
    // 定义泛型
    fun <T> method(param: T): T {
        return param
    }

    // 定义带上界的泛型
    fun <T : Number> numberMethod(param: T): T {
        return param
    }
}

// 使用
MyClass().method(666)

实际上,默认的泛型定义也是带有上界的,默认的上界是 Any?,所以默认的泛型是可以指定为可空类型的,如果需要让泛型不可空,只需手动将上界指定为Any即可。

应用泛型方法,我们可以自定义一个 apply 函数

fun <T> T.myApply(block: T.() -> Unit): T {
    block()
    return this
}

其中, T.() -> Unit 中的 T. 表示在此函数中提供 T 类型的上下文,也就是说在函数中可以直接调用属于 T 类型的方法。

我们自定义的这个 myApply 方法和 Kotlin 标准函数中的 apply 方法的使用方式和效果是一模一样的。

fun main() {
    val a = StringBuilder().myApply {
        append("666")
    }
    // 输出 666
    println(a.toString())
}

6.3 泛型实化

6.3.1 类型擦除机制

在 JDK1.5 以前,Java 还没有泛型功能,List 之类的数据结构中可以存储任意类型的数据,取数据时必须向下转型,很容易出现类型转换异常。

JDK1.5 中引入了泛型,使得 Java 代码更加安全了。但泛型功能是通过类型擦除实现的,也就是说,泛型对于类型的约束仅在编译期有效,运行时仍然会按照 JDK1.5 之前的机制来运行。

类型擦除使得我们无法使用 a is T 或者 T::class.java 这样的语法,因为 T 的实际类型已经在运行时被擦除了。

但由于 Kotlin 的内联函数会在编译时将代码替换到调用它的地方,所以内联函数中的泛型在编译后就变成实际类型了,这就使得 Kotlin 的内联函数中的泛型可以被实化,泛型实化后就成了实际类型,我们也就可以使用 a is T 或者 T::class.java 这样的语法了。

// 在内联函数中使用 reified 关键字将泛型实化
inline fun <reified T> getType() = T::class.java

fun main() {
    // 输出 class java.lang.Integer
    println(getType<Int>())
    // 输出 class java.lang.String
    println(getType<String>())
}

6.3.2 泛型实化的应用

使用泛型实化简化 startActivity

inline fun <reified T> Context.startActivity() = startActivity(Intent(this, T::class.java))
inline fun <reified T> Context.startActivity(block: Intent.() -> Unit) = startActivity(Intent(this, T::class.java).apply(block))

// 使用
startActivity<MainActivity>()
startActivity<MainActivity> {
    putExtra("key", "value")
}

6.4 泛型的协变

当 Student 是 Person 的子类时,我们不能得出ListList的子类这样的结论,因为可能出现类型转换异常,除非这个泛型类在其泛型类型的数据上是只读的。

协变:假如定义了一个 MyClass的泛型类,其中 A 是 B 的子类型,同时 MyClass又是 MyClass的子类型,那么我们就可以称 MyClass 在 T 这个泛型上是协变的。

// out 表示此泛型只能出现在输出位置上,也就是返回值位置上
// 或者是定义成不可变类型 val、或私有变量等等,只要保证不允许外部写,只允许外部读即可
class Data<out T>(val t:T) {
    fun test(): T {
        return t
    }
}

6.5 泛型的逆变

逆变:假如定义了一个 MyClass的泛型类,其中 A 是 B 的子类型,同时 MyClass又是 MyClass的子类型,那么我们就可以称 MyClass 在 T 这个泛型上是逆变的。

// in 表示此泛型只能出现在输入位置上,也就是参数位置上
class Data<in T> {
    fun test(data: T) {
    }
}

如果想要协变(或逆变)的功能,类中又必须使用此泛型作为参数(或返回值),可以使用 @UnsafeVariance 注解使得编译通过,这时,我们需要自己对类型转换异常负责。

注:协变与逆变建议参看郭神的《第一行代码》(第三版)第十章,解释得非常清楚,笔者这里只是做个总结。

七、委托

7.1 类委托

先定义一个 Person 接口,再定义一个 Student 类实现此接口:

interface Person {
    fun eat()
    fun sleep()
}

class Student : Person {
    override fun eat() {
        println("Student is eating.")
    }

    override fun sleep() {
        println("Student is sleeping.")
    }
}

类委托的一般实现

// 委托的意思就是,将此类实现的接口中的所有功能都委托给另一个对象实现
class MyClass(private val person: Person) : Person {
    override fun eat() = person.eat()

    override fun sleep() = person.sleep()
}

fun main() {
    val student = Student()
    val myClass = MyClass(student)
    // 输出 Student is eating.
    myClass.eat()
    // 输出 Student is sleeping.
    myClass.sleep()
}

Kotlin 中,使用 by 关键字就能轻松实现类委托

// Kotlin 中,使用 by 关键字可以轻松实现委托,这种写法实现的功能和上面是一样的
class MyClass(person: Person) : Person by person

某些方法不需要委托,我们也可以重写:

class MyClass(person: Person) : Person by person {
    override fun eat() {
        println("Eat method in MyClass.")
    }
}

fun main() {
    val student = Student()
    val myClass = MyClass(student)
    // 输出 Eat method in MyClass.
    myClass.eat()
    // 输出 Student is sleeping.
    myClass.sleep()
}

使用委托的好处是我们可以轻易地派生出一个相似的类,并按需修改其中的部分方法。效果上和继承很像,但委托更加的灵活,不会造成继承之后子类与父类的强关联。并且由于委托的是接口的实现,所以我们可以轻易地替换具体的实现类。

7.2 属性委托

class MyClass {
    // 类中的属性委托
    private val fieldInClass by OtherClass()
    fun test() {
        // 方法中的属性委托
        val fieldInMethod by OtherClass()
        println(fieldInClass)
        println(fieldInMethod)
    }
}

class OtherClass {
    // 使用 operator 修饰的 getValue 方法实现委托。
    // 第一个参数表示此委托可以在什么类中使用,这里设置成 Any 表示任何类中都可使用,第二个参数暂时用不上
    operator fun getValue(any: Any, property: KProperty<*>): Any {
        return 1
    }

    // 用于方法中的属性委托
    operator fun getValue(nothing: Nothing?, property: KProperty<*>): Any {
        return 2
    }
}


fun main() {
    val myClass = MyClass()
    // 输出 1 2
    myClass.test()
}

Kotlin 内置了 lazy 函数,使得我们可以轻松地实现懒加载,它的内部就是使用属性委托实现的

val field by lazy { 1 }

使用属性委托,我们可以定义出自己的 lazy 函数

class MyClass {
    val field by MyLazy {
        println("field init")
        1
    }
}

class MyLazy<T>(private val block: () -> T) {
    var value: T? = null
    operator fun getValue(myClass: MyClass, property: KProperty<*>): T {
        value ?: let {
            value = block.invoke()
        }
        return value!!
    }
}

fun main() {
    // 先输出 main,再输出 field init,说明懒加载成功了
    val myClass = MyClass()
    println("main")
    myClass.field
}

这里只是仿照 lazy 实现了简易版的懒加载,实际上 Kotlin 内置的 lazy 函数还处理了线程安全问题,项目中还是应该使用内置的 lazy 函数。

八、中缀函数

// 只有一个参数的扩展函数添加 infix 关键字后,使用时就可以用中缀语法糖
infix fun String.add(s: String) = this + s

fun main() {
    // 中缀语法糖
    val string = "123" add "456"
}

Kotlin 中的 to 函数就是一个中缀函数

val map = mapOf(1 to "one", 2 to "two")

to 函数的源码如下

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

A to B 实际上就是调用的A.to(B)方法。

你可能感兴趣的:(Kotlin)