每天学一点 Kotlin -- 多彩的类:泛型
----《第一季Kotlin崛起:次世代Android开发 》学习笔记
总目录:每天学一点 Kotlin ---- 目录
上一篇:每天学一点 Kotlin -- 多彩的类:密封类
下一篇:每天学一点 Kotlin -- 多彩的类:嵌套类
1. 泛型
1.1 Kotlin 中也有泛型的概念,和 Java 中的类似,但又不尽相同。先来看两种场景
(1) 第一种场景:
fun testCommon01(value: Int) {
println("the value is $value")
}
fun testCommon01(value: Double) {
println("the value is $value")
}
fun testCommon01(value: String) {
println("the value is $value")
}
fun main() {
testCommon01(10)
testCommon01(10.11)
testCommon01("11.01")
}
打印结果:
the value is 10
the value is 10.11
the value is 11.01
上面的代码中,重载貌似也看着很方便实现,但是还是写了大量重复的代码。
(2) 第二种场景:
fun<T> testCommon02(value: T) {
println("testCommon02() -- the value is $value")
}
fun main() {
testCommon02(10)
testCommon02(10.11)
testCommon02("11.01")
}
打印结果:
testCommon02() -- the value is 10
testCommon02() -- the value is 10.11
testCommon02() -- the value is 11.01
1.2 如上面第二种场景所示:通过一个占位符,来匹配不同类型,这种形式就是泛型。
2. 基本使用
2.1 通常我们会在类、接口、方法中声明泛型:
2.1.1 泛型类
class Animal<T> {}
// 或者
class Animal<T>(par: T) {}
2.1.2 泛型接口
interface IAnimal<T> {}
2.1.3 泛型方法
fun<T> initAnimal(param: T) {}
2.2 在上面的第二种场景中,使用了泛型方法,下面学习泛型类和泛型接口
2.2.1 使用泛型类:
class TestCommonData1<T>(value: T) {
var v = value
fun printInfo() = println("TestCommonData1<T>(value: T) --- value = $v")
}
fun main() {
val tc1 = TestCommonData1<Int>(100)
tc1.printInfo()
val tc2 = TestCommonData1<Double>(100.001)
tc2.printInfo()
val tc3 = TestCommonData1<String>("100-100")
tc3.printInfo()
}
打印结果:
TestCommonData1<T>(value: T) --- value = 100
TestCommonData1<T>(value: T) --- value = 100.001
TestCommonData1<T>(value: T) --- value = 100-100
2.2.2 使用泛型接口:
interface TestCommonInterface1<T> {
fun printMsg(msg: T)
}
class TestCommonData2 : TestCommonInterface1<Int> {
override fun printMsg(msg: Int) {
println("TestCommonData2() -- msg = $msg")
}
}
class TestCommonData3 : TestCommonInterface1<String> {
override fun printMsg(msg: String) {
println("TestCommonData3() -- msg = $msg")
}
}
fun main() {
val tc2 = TestCommonData2()
val tc3 = TestCommonData3()
tc2.printMsg(200)
tc3.printMsg("300-300")
}
打印结果:
TestCommonData2() -- msg = 200
TestCommonData3() -- msg = 300-300
上述使用泛型接口中,跟 1.1 中第一种场景相似,也是写了很多重复的代码。所以可以把泛型接口中的代码进行优化:
class TestCommonData4<T> : TestCommonInterface1<T> {
override fun printMsg(msg: T) {
println("TestCommonData3() -- msg = $msg")
}
}
fun main() {
val tc4_1 = TestCommonData4<Int>()
tc4_1.printMsg(40)
val tc4_2 = TestCommonData4<Double>()
tc4_2.printMsg(40.04)
val tc4_3 = TestCommonData4<String>()
tc4_3.printMsg("40-40")
}
打印结果:
TestCommonData3() -- msg = 40
TestCommonData3() -- msg = 40.04
TestCommonData3() -- msg = 40-40
3. out 关键字:
3.1 先来看一段代码:
interface TestCommonInterface2<T> {
fun create(): T
}
//fun change2(f: TestCommonInterface2<String>) {
// val factoryAny: TestCommonInterface2<Any> = f // 错误的写法
//}
这段代码理论上是没有问题的,但是其实编译不通过。因为这种情况下如果 T 作为函数的参数是不可以的,因为 T 对参数的类型做了更多的限制,而 Any 太宽泛了。然而,编译器并不知道我们的 T 只是用在方法的返回值中。也就是说,其实在这里,Factory<T> 转变为 Factory<String> 是没有问题的,只是编译器多虑了。那么如何让编译器信任我们呢,可以把代码改成如下:
interface TestCommonInterface3<out T> {
fun create(): T
}
fun change3(f: TestCommonInterface3<String>) {
val factoryAny: TestCommonInterface3<Any> = f
}
即,只要在参数T前面加上 out 标识符,这是在告诉编译器这个类型只是用在输出上,也就是方法的返回值,并没有用在任何输入的情况中,这样编译就通过了。
4 in 关键字
4.1 先来看一段代码:
interface Factory<T>{
fun produce(ele: T)
}
fun change(f: Factory<Any>){
val factory_any: Factory<Any> = f
}
4.2 上面这段代码仍然是在编译时不会通过的。但是理论上是可以的,因为 String 根本还是继承了 Any 这个类,我们原本用 Any 作为参数类型,当然 String 也是符合这个参数类型的,只是参数做了更多的限制。因此,这种转变在这种情况下是完全没有问题的,因为在接口中,我们把 T 用在了输入中。如果任然想要编译器信任我们,需要把代码改为以下:
interface Factory<in T>{
fun produce(ele: T)
}
即,在参数前面加了 in 标识符,也就是告诉编译器我们的 T 只会是输入。
5. 总结
5.1 其实在 Kotlin 中,in 和 out 不止告诉编译器这个类型存在的位置,也分别有一个好听的名称,就是“消费者”和“生产者”。
5.2 限制类型:因为 T 这个通配符是包罗万象的,代表了所有的类型。但是在有些情况下并不是所有的类型都适合的,比如说一个实现计算器功能的方法,T 应该是 Int,Double,Float 等这些可以进行运算的类。所以我们要对 T 这个类型做一些约束以实现一些功能。比如约束 T 已经要实现某些接口或继承某个类。