Kotlin泛型类型参数

Kotlin泛型类型参数

    泛型允许你定义带类型参数的类型。当这种类型的实例被创建出来的时候,类型参数被替换成称为类型实参的具体类型。例如,如果有一个List类型的变量,弄清楚这个列表中可以存储哪种事物是有意义的。类型参数可以准确清晰地进行描述,就像这样“这个变量保存了字符串列表”,而不是“这个变量保存了一个列表”。Kotlin说明“字符串列表”的语法和Java看起来一样:List<String>。还可以给一个类声明多个类型参数。例如,Map类就有键类型和值类型这个两个参数类型:class Map<K,V>。我们可以用具体的类型实参来实例化它:Map<String,Person>。目前,多有概念都和Java没什么不一样。

    和一般类型一样,Kotlin编译器也常常能推导出实参类型:

val authors= listOf("Dmitry","Svetlana")

    因为传给listOf函数值都是字符串,编译器推导出你正在创建一个List<String>。另一方面,如果你想创建一个空的列表,这样就没有任何可以推导出类型实参线索,你就要显示的指定它。就创建列表来说,即可以选择在变量声明中说明泛型的类型,也可以在穿件列表的函数中说明类型实参。

  val readers: MutableList<String> = mutableListOf()

  val readers = mutableListOf<String>()

    两种声明是等价的。

1.泛型函数和属性

    如果要编写一个很使用列表的函数,希望它可以在任何列表中使用,而不是某个具体类型的元素列表,需要编写一个泛型函数。泛型函数有它自己的类型参数。这些类型形参在每次函数调用时都必须替换成具体的类型实参。

    大部分使用集合的库函数都是泛型的。

fun <T> List<T>.slice(indices:IntRange):List<T>

    接收者和返回类型用到了函数类型的形参T,它们的类型都是List<T>。当你在一个具体的列表上调用这个函数时,可以显示地指定实参。但是大部分情况下你不必这样做,因为编译器会推导出类型。

    调用泛型函数

val letters = ('a'..'z').toList()
LogS(letters.slice<Char>(0..2))//[a, b, c]
LogS(letters.slice(10..13))//[k, l, m, n]

    这两次调用的结果都是List<Char>。编译器吧函数返回类型List<T>中的T替换成推导的类型Char。

    调用泛化的高阶函数

 val authors = listOf("Dmitry", "Svetlana")
 val readers = mutableListOf<String>(/*....*/)

 fun <T> List<T>.filter(predicate: (T) -> Boolean): List<T>
 readers.filter { it in authors }

    这个例子中自动生成的lambda参数it的类型是String。编译器必须把它推导出来:毕竟,在函数声明中lambda参数是泛型类型T。编译器推断T就是String,因为它知道函数应该在List<T>上调用,而它的接受者readers的真实类型是List<String>。

    可以给类或接口的方法、顶层函数,以及拓展函数声明类型参数,在前面的例子中,类型参数用在了接收者和lambda参数的类型上。

    还可以用同样的语法声明泛型的拓展属性。例如下面这个返回列表倒数第二个元素的拓展属性:

 val<T> List<T>.penultimate:T
      get() = this[size-2]
 LogS(listOf(1,2,3,4).parallelStream())

2.声明泛型类

    和Java一样,Kotlin通过在类名称后加上一对尖括号,并把类型参数放在尖括号内来声明泛型类及泛型接口。一旦声明之后,就可以在类的主体内像其他类型一样使用类型参数。我们来看看标准java接口List如何使用Kotlin来声明。我们省去了大部分方法定义,让例子变得简单:

  interface List<T>{
        operator fun get(index:Int):T
    }

    如果你的类继承了泛型类,你就得为基础类型的泛型形参提供一个类型实参。它可以是具体类型或者另一个类型形参:

class StringList: MainActivity.List<String> {
    override fun get(index: Int): String ="..."
}

class String:Comparable<String>{
    override fun compareTo(other: String): Int =2
}

    StringList类型声明成只能包含String元素,所以它使用String作为基础类型的类型实参。子类中的任何函数都要用正确的类型替换掉T,所以在StringList中你会得到函数签名get(Int):Srting,而不是fun get(Int):T。

    而类ArrayList定义了它自己的类型参数T并把它指定为父类的类型实参。注意ArrayList<T>中的T和List<T>中的T不一样,它是全新的类型实参,不必保留一样的名称。

    一个类甚至可以把它自己作为类型实参引用。实现Comparable接口的类就是这种模式的经典例子。任何可以比较的元素都必须定义如何与同样类型的对象比较:

interface Comparavle<T>{
    fun compareTo(other:T):Int
}

class String:Comparable<String>{
    override fun compareTo(other: String): Int =/*q*/
}

    String类实现了Comparable泛型接口,提供类型String给类型实参T。

    迄今为止,泛型和Java中看起来差不多。

3.类型参数约束

    类型参数约束可以限制作为(泛型)类和(泛型)函数的类型实参的类型。以计算列表元素之和的函数为例。它可以用在List<Int>和List<Double>上,但不可以用在List<String>这样的列表上。可以定义一个类型参数约束,说明sum的类型形参必须是数字,来表达这个限制。

    如果你把一个类型指定为泛型类型形参的上界约束,在泛型类型具体的初始化中,其对应的类型实参就必须是这个具体类型或者它的子类型。

    你是这样定义约束的,把冒号放在类型参数名称之后,作为类型参数上界的类型紧随其后。在Java中,用的是关键字extends来表达一样的概念:<T extends Number> T sum(List<T> list)。

    这次函数调用时允许的,因为具体类型实参继承了Number:

    一旦指定类型形参T的上界,你就可以把类型T的值当做它的上界(类型)的值使用。

fun <T : Number> oneHalf(value: T): Double {
    return value.toDouble() / 2.0
}
LogS(oneHalf(3))//1.5

     声明带类型参数约束的函数 

fun <T:Comparable<T>> max(first:T,second:T):T{
    return if (first>second) first else second
}
LogS(max("kotlin","java"))//kotlin

    当你试图对不能比较的条目条用max方法时,代码不会编译:

LogS("kotlin",42)

    T的上界是泛型类型Comparable<T>。前面已经看到了,String类型继承了Comparable<String>,这使得String变成了max函数的有效类型实参。

    记住,first>second的简写形式会根据Kotlin的运算符约定编译成first.compareTo(second)>0。这种比较之所以可行,是因为first的类型T继承自Comparable<T>,这样你就可以比较first和另外一个类型T的元素。

    极少数情况下,需要在一个类型参数上指定多个约束,这时你需要使用稍微不同的语法。

    为一个类型参数指定多个约束 

   fun <T> ensureTrailingPeriod(seq:T)
        where T:CharSequence,T:Appendable{
        if (!seq.endsWith('.')){
            seq.append('.')
        }
    }
 val helloWorld=StringBuilder("Hello world")
 ensureTrailingPeriod(helloWorld)
 LogS(helloWorld)//Hello world.

    这种情况下,可以说明作为类型实参的类型必须实现CharSequence和Appendable两个接口。这意味着该类型的值可以使用访问数据和修改数据两种操作。

4.让类型形参非空

    如果你声明的是泛型类或者泛型函数,任何类型实参,包括那些可空的类型实参,都可以替换它的参数类型实参。事实上没有指定上界的类型形参将会使用Any?这个默认上界。

 class Processor<T>{
        fun process(value:T){
            value?.hashCode()
        }
    }

    process函数中,参数Value是可空的,尽管T并没有使用问号标记。下面这种情况是因为Processor类具体初始化时T能使用可空类型:

al nullableStringProcess=Processor<String?>()
nullableStringProcess.process(null)

    如果你想保证替换类型形参的始终是非空类型,可以通过指定一个约束对象来实现。如果你除了可控性之外没有任何限制,可以使用Any代替默认Any?作为上界:

 class Processor<T:Any>{
        fun process(value:T){
            value?.hashCode()
        }
    }

    约束<T:Any>确保了类型T永远是非空类型。编译器不会接收代码Processor<Stirng?>,因为类型实参String?不是Any的子类型。

   注意可以通过指定任意非空类型作为上界,来让类型参数非空,不光是类型Any。

    到目前为止,我们已经介绍了泛型的基础概念——那些和Java最接近的主题。


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值