Kotlin快速入门系列8

Kotlin 的泛型

与Java一样,Kotlin也提供泛型。泛型,即 "参数化类型",将类型参数化,可以用在类,接口,方法上。可以为类型安全提供保证,消除类型强转的烦恼。声明泛型类的格式如下:

Kotlin 复制代码
class Box<T>(t: T) {
    var value = t
}

用泛型创建类的实例的时候需要指定类型参数:

Kotlin 复制代码
val box: Box<Int> = Box<Int>(100)
// 或者
val box = Box(100) // 声明泛型类时候编译器会进行类型推断,100的类型是Int,所以编译器知道我们说的是 Box<Int>。

定义泛型类型变量,可以完整地写明类型参数。如果定义泛型类的时候指定了泛型类型,则编译器可以自动推断类型参数,定义时就可以省略类型参数。

Kotlin 复制代码
fun <T> boxIn(value: T) = Box(value)

val box4 = boxIn<Int>(1)
val box5 = boxIn(1)     // 编译器会自动进行类型推断

在调用泛型函数时,如果可以推断参数类型,就可以省略泛型参数。

例如如下示例,泛型函数根据传入的不同类型做相应处理:

Kotlin 复制代码
fun <T> printType(content: T) {

    when (content) {
        is Int -> println("整型参数为 $content")
        is String -> println("字符串参数转换为大写:${content.toUpperCase()}")
        else -> println("传入参数 T 既不是整型,也不是字符串")
    }
}

fun main(args: Array<String>) {
    val num = 111
    val name = "Weyen"
    val bool = true

    printType(num)    // 整型
    printType(name)   // 字符串类型
    printType(bool)   // 布尔型
}

对应的输出结果为:

泛型约束

跟Java一样,Kotlin也拥有泛型约束。在Java中,使用extends关键字指明上界。在kotlin中使用:对泛型的类型上限进行约束。最常见的约束是上界(upper bound)。

例如,下面的代码中,调用num()函数时,传入的参数只能是Number及其子类,如果是其他类型,则会报错:

Kotlin 复制代码
fun <T : Number> sum(vararg param: T) = param.sumByDouble { it.toDouble() }
fun main() {
    val va1 = sum(1,10,0.6)
    val va2 = sum(1,10,"kotlin") // 这里会提示编译错误
}

默认的上界是Any?。(注意,这里是Any?不是Any。Any 类似于 Java 中的 Object,它是所有非空类型的超类型。但是 Any 不能保存 null 值,如果需要 null 作为变量的一部分,则需要使用 Any?。Any?是 Any 的超类型,所以 Kotlin 默认的上界是 Any?)

如果有多个上界约束条件,可以用 where 子句:

Kotlin 复制代码
open class ClassA
interface InterfaceB
class TypeClass<T>(var variable: Class<T>) where T : ClassA, T : InterfaceB

型变

Kotlin 中没有像java一样的<? extends T>这样的通配符,也没有父类向子类转换,取而代之的是两个其他的东西:声明处型变(declaration-site variance)与类型投影(type projections)。

声明处型变 ****:****声明处的类型变异使用协变注解修饰符:in、out (消费者 in, 生产者 out。也可以这样理解:in,就是只能作为传入参数的参数类型;out, 就是只能作为返回类型参数的参数类型。)

相对于Java的概念:

o ut 协变 :类型向上转换,像java中的子类向父类转换。

in 逆变 :类型向下转换,父类向子类转换。

协变类型参数只能用作输出,可以作为返回值类型但是无法作为入参的类型:

Kotlin 复制代码
// 支持协变的类
class KotlinChange<out A>(val demo: A) {
    fun foo(): A {
        return demo
    }
}

fun main(args: Array<String>) {
    var strCo: KotlinChange<String> = KotlinChange("a")
    var anyCo: KotlinChange<Any> = KotlinChange<Any>("b")
    anyCo = strCo
    println(anyCo.foo())   // 对应的控制台输出 a
}

逆变类型参数只能用作输入,可以作为入参的类型但是无法作为返回值的类型:

Kotlin 复制代码
// 这个类支持逆变,注意这里的in
class KotlinChange<in A>(num: A) {
    fun foo(num: A) {
    }
}

fun main(args: Array<String>) {
    var strDCo = KotlinChange("a")
    var anyDCo = KotlinChange<Any>("b")
    strDCo = anyDCo
    println(strDCo.foo())  //这里就会报错了
}

星号投影(star-projection)

(星号(型)投影(射),单词翻译过来的叫啥都有,能明白意思就行)

在Kotlin 的泛型封装里,会出现 <*> ,这称为星号投影语法,用来表明"不知道关于泛型实参的任何信息"。

<*>星号投影,表示"不知道关于泛型实参的任何信息",在修饰容器时,因为不知道是哪个类型,所以并不能向容器中写入任何东西(写入的任何值都可能会违反调用代码的期望)。读取值是可以的,因为所有存储在列表中的值都是Any?的子类。<*>星型投影,修饰的容器(比如:MutableList,MutableMap ),只能读不能写。 相当于<out Any?>。

比如:MutableList<*> 表示的是 MutableList<out Any?>

如果一个泛型类型中存在多个类型参数, 那么每个类型参数都可以单独的投影. 比如, 如果类型定义为interface Function<in T, out U> , 那么可以出现以下几种星号投影:

1、Function<*, String> , 代表 Function<in Nothing, String> ;

2、Function<Int, *> , 代表 Function<Int, out Any?> ;

3、Function<, > , 代表 Function<in Nothing, out Any?> 。

如下示例:

Kotlin 复制代码
class KotlinBean<T>(val t: T, val t2 : T, val t3 : T)
class FruitBean(var name : String)
fun main(args: Array<String>) {
    val a1: KotlinBean<*> = KotlinBean(12, "String", FruitBean("苹果"))   //星号投影
    val a2: KotlinBean<Any?> = KotlinBean(12, "String", FruitBean("苹果"))   //和a1是一样的
    val apple = a1.t3    //参数类型为Any
    println(apple)
    val apple2 = apple as FruitBean   //强转成FruitBean类
    println(apple2.name)
    //使用数组
    val list:ArrayList<*> = arrayListOf("String",1,3.14f,FruitBean("苹果"))
    for (item in list){
        println(item)
    }
}

对应的输出为:

这时我们可以换个角度理解,关于星号投影,其实就是*代指了所有类型,相当于Any?。

End,如有问题请留言讨论。

相关推荐
奋斗的小花生1 分钟前
c++ 多态性
开发语言·c++
魔道不误砍柴功3 分钟前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
闲晨7 分钟前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程34 分钟前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
数据猎手小k2 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
萧鼎3 小时前
Python并发编程库:Asyncio的异步编程实战
开发语言·数据库·python·异步