Android—Kotiln基础教程(七)

4,487 阅读9分钟

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

前言

在上一篇中,讲解了Kotlin对应的对象,接口,抽象类相关的知识点。在这一篇中,将会讲解Kotlin对应的泛型、扩展函数。

话不多说,直接开始!

1. 泛型

1.1 单泛型参数

open class Human(val age: Int)

class Boy(val name: String, age: Int) : Human(age)
class Man(val name: String, age: Int) : Human(age)
class Dog(val weight: Int)

class MagicBox<T>(item: T) {
    var available = false
    private var subject: T = item
    fun fetch(): T? {
        // takeIf 如果表达式成立,返回 使用者 subject 对象,否则返回null 对象
        return subject.takeIf { available }
    }
}


fun main{
    val box1: MagicBox<Boy> = MagicBox(Boy("Jack", 20))
    val box2: MagicBox<Dog> = MagicBox(Dog(12))

    println("box1 $box1")
    box1.available = true

    box1.fetch()?.run {
        println("this is ${this.javaClass.name} your name = $name")
    }

    println("box2 $box2")
    box2.available = true

    box2.fetch()?.run {
        println("this is ${this.javaClass.name} your weight = $weight")
    }
}

运行效果

box1 MagicBox@51016012
this is Boy your name = Jack
box2 MagicBox@29444d75
this is Dog your weight = 12

这个使用方式和java类似,使用方式也挺简单的,从头看到这的小伙伴,这点代码相信能够直接一眼而过,直接下一个!

1.2 多泛型参数

1.2.1 示例一

open class Human(val age: Int)

class Boy(val name: String, age: Int) : Human(age)
class Man(val name: String, age: Int) : Human(age)
class Dog(val weight: Int)

class MagicBox<T>(item: T) {
    var available = false
    private var subject: T = item
   // fun fetch(): T? {
   //     // takeIf 如果表达式成立,返回 使用者 subject 对象,否则返回null 对象
   //     return subject.takeIf { available }
   // }

    fun <R> fetch(subjectModFunction: (T) -> R): R? {
        return subjectModFunction(subject).takeIf { available }
    }
}

fun main{
    val box: MagicBox<Boy> = MagicBox(Boy("Jack", 20))
    box.available = true
    val man = box.fetch() {
        Man(it.name, it.age.plus(10))
    }
    man?.let { println("${it.name},${it.age}") }
}

运行效果

Jack,30

现在我们看到,这次使用 了MagicBox里的fun <R> fetch方法,在这个方法里拥有两个泛型:一个是T,一个是R,其中T泛型,我们知道是由外部传入而决定的;而R泛型,确实由 return subjectModFunction(subject).takeIf { available }这句代码决定。

如果说available 为true时,那么R类型就为subjectModFunction方法返回的结果类型;否则就为Null!

subjectModFunction返回的类型,由这个方法而决定!所以说在对应方法的闭包里最后表达式是什么类型,那这方法就是什么类型!这和之前讲解的let相似!

 T.let(block: (T) -> R): R {}

1.2.2 示例二

open class Human(val age: Int)

class Boy(val name: String, age: Int) : Human(age)
class Man(val name: String, age: Int) : Human(age)
class Dog(val weight: Int)

class MagicBox2<T : Human>(vararg item: T) {
    var available = true
    private var subject: Array<out T> = item
    fun fetch(index: Int): T? {
        return subject[index].takeIf { available }
    }

    fun <R> fetch(index: Int, subjectModFunction: (T) -> R): R? {
        return subjectModFunction(subject[index]).takeIf { available }
    }

    operator fun get(index: Int): T? = subject[index].takeIf { available }
}

fun main() {
    val box1 = MagicBox2(
        Boy("Jack", 15)
        , Man("Bob", 20),
        Human(12)
    )
    box1.fetch(2)?.run {
        println("your age is $age")
    }
    
    println("-----------------------")

    box1.fetch(0) {
        var it = it as Boy
        var man= Man(it.name, it.age.plus(3))
        "我已经成年了,我年龄为: ${man.age}"
    }.run(::println)

    println("-----------------------")

    box1[1]?.run { println("your age is $age") }
}

运行效果

your age is 12
-----------------------
我已经成年了,我年龄为: 18
-----------------------
your age is 20

其实这个示例重点关注class MagicBox2<T : Human>(vararg item: T) 这里面的vararg 关键字,它的意思有点像java里面的类型Human ...,意思就是在对应方法里,形参可以传多个进来。里面的subject 自然而然变成了对应的数组类型。

注意看MagicBox2里面的private var subject: Array<out T> = item这段代码,这里居然用了out,一旦吧out删掉直接报语法错误!这是什么情况呢?

这就是马上要讲解的:协变&逆变!

1.3 协变out & 逆变in

那么为什么要有这个东西?何为协变?何为逆变?

这就要从泛型擦除开始说起了!

那么何为泛型擦除?你怎么每句话都要和我杠?没错!我就是杠!

1.3.1 泛型擦除

来看看这个例子

fun main{
    val list1 = ArrayList<String>()
    val list2 = ArrayList<Int>()
    println(list1.javaClass.name == list2.javaClass.name)
    println(list1.javaClass.name.equals(list2.javaClass.name))
    println(list1.javaClass.name === list2.javaClass.name)
}

按理说list1 list2 在这肯定不相等!毕竟泛型都不一样!来看看运行结果:

true
true
true

嗯???为什么会这样呢?来看看ByteCode

1.png

如图所示

我们定义的list里面的泛型统统没有了!这是因为在Kotlin以及Java里,对应的泛型都是伪泛型!既然编译后泛型要被擦除,那我在ArrayList<Int>集合里面直接添加字符串试试?

2.png

系统肯定不会让你这样做的,但是你阔以通过反射来实现ArrayList<Int>集合添加字符串。

那是不是泛型不同,就不能添加对应的元素?试试看效果?

open class Fruit(val weight: Int)

open class Apple(val name: String, weight: Int) : Fruit(weight)

class RedApple(name: String, weight: Int) : Apple(name, weight)

fun main{
    var fruitList=ArrayList<Fruit>()
    fruitList.add(RedApple("aaa",12))
    fruitList.add(Apple("bbb",33))
    fruitList.add(Fruit(10))
}

这里我们看到Fruit作为Apple的父类,而RedApple作为Apple的子类,却发现只要是Fruit子类的元素都能正常添加。那将泛型Fruit换成RedApple会怎样呢?

fun main{
    var fruitList=ArrayList<RedApple>()
    fruitList.add(RedApple("aaa",12))
    fruitList.add(Apple("bbb",33)) // 提示语法错误
    fruitList.add(Fruit(10))// 提示语法错误
}

用另一种写法试试看呢?

fun main{
    var fruitList = ArrayList<Fruit>()
    fruitList.add(RedApple("aaa", 12))

    var fruitList1 = ArrayList<RedApple>()
    fruitList1.addAll(fruitList)// 提示语法错误
}

发现还是报错,这是怎么回事呢?

此时就迎来了PECS准则:即如果参数化类型表示一个T的生产者,使用协变out;如果表示一个T的消费者,使用逆变in

那么何为协变?何为逆变?

1.3.2 协变out & 逆变in

示例一,声明处形变

interface Box<out T> {//只读不可写 协变

    fun putFruit(t: T)
    //              ↑
    //此处报错 Type parameter T is declare as 'out'
    //       but occurs in 'in' position in type T

    fun getFruit(): T
    //              ↑
    // 放在返回值的位置就不会报错
}
interface Box1<in E> {//只写不可读 逆变

    fun putFruit(t: E)
    //              ↑
    // 放在形参的位置就不会报错

    fun getFruit(): E
    //              ↑
    //此处报错 Type parameter E is declare as 'in'
    //       but occurs in 'out' position in type E
}
interface Box2<E> {

    fun putFruit(t: E)
    //              ↑
    // 放在形参的位置就不会报错

    fun getFruit(): E?
    //              ↑
    // 放在返回值的位置就不会报错
}

从这里我们能看到

  • 当泛型使用out时,对应泛型只读不可写
  • 当泛型使用in时,对应泛型只写不可读
  • 要想泛型可读可写,那就不使用out和in

继续看下一个示例:

示例二:使用时形变

interface Box2<E> {
    fun putFruit(t: E)
    //              ↑
    // 放在形参的位置就不会报错
    fun getFruit(): E?
    //              ↑
    // 放在返回值的位置就不会报错
}
class FruitBox2<T> : Box2<T> {
    var t: T? = null
    override fun putFruit(t: T) {
        this.t = t
    }
    override fun getFruit(): T? {
        return this.t
    }
}

fun putFruitForBox(box2: Box2<in RedApple>) {// Kotlin逆变 类似于 ? super RedApple 用客套话说:只要在RedApple之上的对象不管多少级都可以接受
    box2.putFruit(RedApple("put红苹果", 12))
}

fun getFruitForBox(box2: Box2<out Fruit>): Fruit? { // Kotlin协变  类似于 ? extends Fruit 用客套话说:只要在Fruit之下的对象不管多少级都可以接受
    return box2.getFruit()
}

fun main{
    val fruitBox2 = FruitBox2<Fruit>()
    putFruitForBox(fruitBox2)
    
	//这里为了实现协变逆变效果,所以强制转换成 FruitBox2<RedApple>
    val redAppleBox = fruitBox2 as FruitBox2<RedApple>

	//因为put时是用的RedApple 对象,所以这里也强转对应类型
    var redApple = getFruitForBox(redAppleBox) as RedApple
    println(redApple?.name)
}

运行效果

put红苹果

分析点:

  1. fun putFruitForBox(box2: Box2<in RedApple>) 这里用到了in RedApple,用通俗话表示只要是RedApple父类,不管多少级,都可以接受,所以 FruitBox2<Fruit>()对应的属性能够调用该方法。
  2. fun getFruitForBox(box2: Box2<out Fruit>) 这里用到了out Fruit,用通俗话表示就是:只要是Fruit子类,不管多少级,都可以接受,所以FruitBox2<RedApple>对应的属性也能调用该方法。

2. 扩展

扩展?什么是扩展?

顾名思义,扩展,就是在对应对象上扩展自己想要的东西,然后可供自己使用。

这么神奇的么?来试试?连Java都没有这玩意!

2.1 属性扩展

//给 String 额外提供了 addExt 方法
fun String.addExt(amount: Int = 1) = this + "!".repeat(amount)
//给所有成员 都提供了 easyPrint 方法用来打印当前对象
fun <T> T.easyPrint() = println(this)

fun main{
    println("abc".addExt(10))
    123.easyPrint()
    //这里表示扩展后,依然能够正常使用Kotlin提供的API
    "abc".addExt(10).easyPrint().also { }.let { }
}

运行效果

abc!!!!!!!!!!
123
abc!!!!!!!!!!

是不是很好玩呢?系统核心的对象你都能在之上进行扩展!

既然能这样!那我是不是可以在对应对象里加入非空判断的扩展函数?

2.2 方法扩展

fun String?.printWithDefault(default: String) = println(this ?: default)

fun main{
    val nullableString: String? = null
    nullableString.printWithDefault("abc")
}

从这个代码上看,扩展函数的意思就是如果说,当前对象为null,那么就返回默认值!

那么问题来了,这样写,如果是Int类型的对象为null该怎么办呢?

于是乎带着问题重新新增了一个扩展!

fun String?.printWithDefault(default: String) = println(this ?: default)
fun Int?.printWithDefault(default: Int) = println(this ?: default)

fun main{
    val nullableString: String? = null
    nullableString.printWithDefault("abc")
    val nullableInt:Int?=null
    nullableInt.printWithDefault (12)
}

运行效果

abc
12

看到这运行效果,果然还真是按照这想法来的。但看着看着这样写还是有点java化。

想看看最终效果是咋样的。

infix fun String?.printWithDefault(default: String) = println(this ?: default)
infix fun Int?.printWithDefault(default: Int) = println(this ?: default)

fun main{
    val nullableString: String? = null
//    nullableString.printWithDefault("abc")
    nullableString printWithDefault "cba"
    val nullableInt:Int?=null
//    nullableInt .printWithDefault (12)
    nullableInt printWithDefault 13
}

运行效果

cba
13

注意看区别!在原有扩展前加入了infix 关键字,然后在使用的时候,没有使用.方法()的方式了,而是用空格分隔就能调用对应的方法以及传入对应的参数。

那么现在问题来了!

  • 对应的扩展是针对当前类,还是全局类呢?
  • 相同对象在不同地方定义同一扩展会怎样?
  • 是否跟属性或者方法一样有对应的private/public等修饰符?

3.3 解疑问题

1.png

//集合扩展,调用该方法将会先把集合内里面的元素打乱,随后取第一个元素
fun <T> Iterable<T>.randomTake(): T = this.shuffled().first()

如图所示

在这里创建了一个新类,里面定义对应的扩展函数。意思就是将集合内里面的元素打乱,随后取第一个元素。进入另一个类,来看看使用体验:

fun main{
    val list = listOf("Jason", "Jack", "Tom")
    list.randomTake().run(::println)
}

居然发现能够正常使用,运行看下效果哇:

Jack

也就是说,默认的扩展是全局有效。现在验证下第二个问题!

fun <T> Iterable<T>.randomTake(): T = this.shuffled().first()

fun main() {
    val list = listOf("Jason", "Jack", "Tom")
    list.randomTake().run(::println)
}

当吧扩展复制粘贴到当前类的一瞬间,代码爆红了!很明显这种方式不行!

那阔不阔以通过特殊修饰符(private/public等)来实现相同对象在不同地方定义同一扩展呢?

进入Test.kt

//集合扩展,调用该方法将会先把集合内里面的元素打乱,随后取第一个元素
private fun <T> Iterable<T>.randomTake(): T = this.shuffled().first()

发现代码依旧报红!

进入KotlinStudy08.kt

private fun <T> Iterable<T>.randomTake(): T = this.first()

fun main{
    val list = listOf("Jason", "Jack", "Tom")
    list.randomTake().run(::println)
}

发现代码正常了,也就是说,自定义扩展默认全局,如果想要实现同一扩展在不同地方呈现不同效果的话,可以将对应相同的扩展都标注为private类型

结束语

好了,本篇到这也结束了!在本篇里相信你对Kotlin对应的泛型以及扩展有所了解!在下一篇里,将会重点讲解Kotlin里面的函数式编程!敬请期待吧!