目录

1、泛型和委托

泛型

类委托和委托属性

by lazy代码块

2、使用infix函数构建更可读的语法

3、泛型的高级特性

泛型实化

泛型的协变

泛型的逆变


1、泛型和委托

泛型

       首先解释一下什么是泛型。在一般的编程模式下,我们需要给任何一个变量指定一个具体的类型,而泛型允许我们在不指定具体类型的情况下进行编程。

       举个例子, List是一个可以存放数据的列表,但是List并没有限制我们只能存放整型数据或字符串数据,因为它没有指定一个具体的类型,而是使用泛型来实现的。也正是如此,我们才可以使用List<Int>、 List<String>之类的语法来构建具体类型的列表。

       泛型主要有两种定义方式:一种是定义泛型类,另一种是定义泛型方法,使用的语法结构都是<T>。当然括号内的T并不是固定要求的,事实上你使用任何英文字母或单词都可以,但是通常情况下, T是一种约定俗成的泛型写法。

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

//调用
val myClass = MyClass<Int>()
val result = myClass.method(123)


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

//调用
val myClass = MyClass()
val result = myClass.method<Int>(123) 
//我们传入了一个Int类型的参数,它能够自动推导出泛型的类型就是Int型,因此这里也可以直接省略泛型的指定
val result = myClass.method(123)

       Kotlin 还允许我们对泛型的类型进行限制。目前你可以将method()方法的泛型指定成任意类型,但是如果这并不是你想要的话,还可以通过指定上界的方式来对泛型的类型进行约束,比如这里将method()方法的泛型上界设置为Number类型

       另外,在默认情况下,所有的泛型都是可以指定成可空类型的,这是因为在不手动指定上界的时候,泛型的上界默认是Any?。而如果想要让泛型的类型不可为空,只需要将泛型的上界手动指定成Any就可以了。
 

类委托和委托属性

       委托是一种设计模式,它的基本理念是:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理。委托功能分为了两种:类委托和委托属性。

类委托:将一个类的具体实现委托给另一个类去完成。

       Kotlin 中委托使用的关键字是by,我们只需要在接口声明的后面使用by关键字,再接上受委托的辅助对象,就可以免去之前所写的一大堆模板式的代码了。

class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
}

       MySet的构造函数中接收了一个HashSet参数,这就相当于一个辅助对象。然后在Set接口所有的方法实现中,我们都没有进行自己的实现,而是调用了辅助对象中相应的方法实现。

       如果我们要对某个方法进行重新实现,只需要单独重写那一个方法就可以了,其他的方法仍然可以享受类委托所带来的便利。

class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
    fun helloWorld() = println("Hello World")
    override fun isEmpty() = false
}

委托属性:将一个属性(字段)的具体实现委托给另一个类去完成。

class MyClass {
    var p by Delegate()
}

       这里使用by关键字连接了左边的p属性和右边的Delegate实例,这种写法代表着将p属性的具体实现委托给了Delegate类去完成。当调用p属性的时候会自动调用Delegate类的getValue()方法,当给p属性赋值的时候会自动调用Delegate类的setValue()方法。

class Delegate {
    var propValue: Any? = null

    //都要使用operator关键字进行声明
    operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
        return propValue
    }
   
    operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
        propValue = value
    }
}

       第一个参数用于声明该Delegate类的委托功能可以在什么类中使用,这里写成MyClass表示仅可在MyClass类中使用(所有类就是 any: Any?);第二个参数KProperty<*>是Kotlin 中的一个属性操作类,可用于获取各种属性相关的值,在当前场景下用不着,但是必须在方法参数上进行声明。另外, <*>这种泛型的写法表示你不知道或者不关心泛型的具体类型,只是为了通过语法编译而已。至于返回值可以声明成任何类型,根据具体的实现逻辑去写就行了;最后一个参数表示具体要赋值给委托属性的值,这个参数的类型必须和getValue()方法返回值的类型保持一致。上述代码只是一种示例写法。

       整个委托属性的工作流程就是这样实现的,现在当我们给MyClass的p属性赋值时,就会调用Delegate类的setValue()方法,当获取MyClass中p属性的值时,就会调用Delegate类的getValue()方法。

       还存在一种情况可以不用在Delegate类中实现setValue()方法,那就是MyClass中的p属性是使用val关键字声明的。如果p属性是使用val关键字声明的,那么就意味着p属性是无法在初始化之后被重新赋值的,因此也就没有必要实现setValue()方法,只需要实现getValue()方法就可以了。
 

by lazy代码块

       by lazy代码块是Kotlin 提供的一种懒加载技术,代码块中的代码一开始并不会执行,只有当p变量首次被调用的时候才会执行,并且会将代码块中最后一行代码的返回值赋给p。by lazy并不是连在一起的关键字,只有by才是Kotlin 中的关键字, lazy在这里只是一个高阶函数而已。

val p by lazy { ... }

2、使用infix函数构建更可读的语法


       我们使用过A to B这样的语法结构构建键值对,其实, to并不是Kotlin 语言中的一个关键字,之所以我们能够使用A to B这样的语法结构,是因为Kotlin 提供了一种高级语法糖特性: infix函数。当然, infix函数也并不是什么难理解的事物,它只是把编程语言函数调用的语法规则调整了一下而已,比如A to B这样的写法,实际上等价于A.to(B)的写法。

       infix函数由于其语法糖格式的特殊性,有两个比较严格的限制:首先, infix函数是不能定义成顶层函数的,它必须是某个类的成员函数,可以使用扩展函数的方式将它定义到某个类当中;其次, infix函数必须接收且只能接收一个参数,至于参数类型是没有限制的。只有同时满足这两点, infix函数的语法糖才具备使用的条件。

       举个例子,比如这里有一个集合,如果想要判断集合中是否包括某个指定元素。新建一个infix.kt 文件,然后编写如下代码:

infix fun <T> Collection<T>.has(element: T) = contains(element)

       我们给Collection接口添加了一个扩展函数,这是因为Collection是Java 以及Kotlin 所有集合的总接口,因此给Collection添加一个has()函数,那么所有集合的子类就都可以使用这个函数了。另外,这里还使用了之前学习的泛型函数的定义方法,从而使得has()函数可以接收任意具体类型的参数。而这个函数内部的实现逻辑就相当简单了,只是调用了Collection接口中的contains()函数而已。也就是说, has()函数和contains()函数的功能实际上是一模一样的,只是它多了一个infix关键字,从而拥有了infix函数的语法糖功能。

现在我们就可以使用如下的语法来判断集合中是否包括某个指定的元素:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
if (list has "Banana") {
    // 处理具体的逻辑
}

3、泛型的高级特性

泛型实化

       具体该怎么写才能将泛型实化呢?首先,该函数必须是内联函数才行,也就是要用inline关键字来修饰该函数。其次,在声明泛型的地方必须加上reified关键字来表示该泛型要进行实化。示例代码如下:

inline fun <reified T> getGenericType() = T::class.java


//调用
fun main() {
    val result1 = getGenericType<String>()
    val result2 = getGenericType<Int>()
}

       上述函数中的泛型T就是一个被实化的泛型,getGenericType()函数直接返回了当前指定泛型的实际类型。

泛型实化的应用

新建一个reified.kt 文件,然后在里面编写如下代码:

inline fun <reified T> startActivity(context: Context, block: Intent.() -> Unit) {
    val intent = Intent(context, T::class.java)
    intent.block()
    context.startActivity(intent)
}

        同时使用inline和reified关键字让泛型T成为了一个被实化的泛型。Intent 接收的第二个参数本来应该是一个具体Activity 的Class类型,但由于现在T已经是一个被实化的泛型了,因此这里我们可以直接传入T::class.java。最后调用Context的startActivity()方法来完成Activity 的启动。

//调用,启动TestA ctivity
startActivity<TestActivity>(context) {
    putExtra("param1", "data")
    putExtra("param2", 123)
}
泛型的协变

       在开始学习协变和逆变之前,我们先了解一个约定。一个泛型类或者泛型接口中的方法,它的参数列表是接收数据的地方,因此可以称它为in位置,而它的返回值是输出数据的地方,因此可以称它为out 位置。

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

       但是如何才能让MyClass<A>成为MyClass<B>的子类型呢?刚才已经讲了,如果一个泛型类在其泛型类型的数据上是只读的话,那么它是没有类型转换安全隐患的。而要实现这一点,则需要让MyClass<T>类中的所有方法都不能接收T类型的参数。换句话说, T只能出现在out 位置上,而不能出现在in位置上。

       在泛型T的声明前面加上了一个out关键字。这就意味着现在T只能出现在out 位置上,而不能出现在in位置上,同时也意味着SimpleData在泛型T上是协变的。


举个例子,首先定义如下3个类:

class SimpleData<out T>(val data: T?) {
    fun get(): T? {
        return data
    }
}  

       由于泛型T不能出现在in位置上,因此不能使用set()方法为data参数赋值,所以这里改成了使用构造函数的方式来赋值。构造函数中的泛型T不也是在in位置上的吗?没错,但是由于这里我们使用了val关键字,所以构造函数中的泛型T仍然是只读的,因此这样写是合法且安全的。另外,即使我们使用了var关键字,但只要给它加上private修饰符,保证这个泛型T对于外部而言是不可修改的,那么就都是合法的写法。

fun main() {
    val student = Student("Tom", 19)
    val data = SimpleData<Student>(student)
    handleMyData(data)
    val studentData = data.get()
}

fun handleMyData(data: SimpleData<Person>) {
    val personData = data.get()
}

       由于SimpleData类已经进行了协变声明,那么SimpleData<Student>自然就是SimpleData<Person>的子类了,所以这里可以安全地向handleMyData()方法中传递参数。

       Kotlin 中的List本身就是只读的,如果你想要给List添加数据,需要使用MutableList 才行。既然List是只读的,也就意味着它天然就是可以协变的。

泛型的逆变

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

协变和逆变的区别如图:

       在泛型T的声明前面加上了一个in关键字。这就意味着现在T只能出现在in位置上,而不能出现在out 位置上,同时也意味着Transformer在泛型T上是逆变的。

interface Transformer<in T> {
    fun transform(t: T): String
}


fun main() {
    val trans = object : Transformer<Person> {
        override fun transform(t: Person): String {
            return "${t.name} ${t.age}"
        }
    }
    handleTransformer(trans) 
}

fun handleTransformer(trans: Transformer<Student>) {
    val student = Student("Tom", 19)
    val result = trans.transform(student)
}

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐