这是我参与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
如图所示
我们定义的list里面的泛型统统没有了!这是因为在Kotlin以及Java里,对应的泛型都是伪泛型!既然编译后泛型要被擦除,那我在ArrayList<Int>
集合里面直接添加字符串试试?
系统肯定不会让你这样做的,但是你阔以通过反射来实现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红苹果
分析点:
fun putFruitForBox(box2: Box2<in RedApple>)
这里用到了in RedApple
,用通俗话表示只要是RedApple
父类,不管多少级,都可以接受,所以FruitBox2<Fruit>()
对应的属性能够调用该方法。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 解疑问题
//集合扩展,调用该方法将会先把集合内里面的元素打乱,随后取第一个元素
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里面的函数式编程!敬请期待吧!