Kotlin中的泛型

Kt中的泛型是一大特色!和Java不太相似,或者说是补齐了Java的坑!

简单的使用泛型,不再赘述!

在Java中,使用泛型的时候,可以使用 extend 关键字表示当前的泛型必须是某个类的子类!才能正常使用! Kt中当然也支持这样的方法! kt中叫上界!

// 这里我们指定了,T的上界是Number ! T 实际的类型,必须是 Number 或者 Number的子类!
fun <T : Number> List<T>.sum() : T
15449441136560.jpg

上界还有一个作用!就是指定泛型为不可空类型!如果你的上界不是一个可空类型,那么你的泛型就不能被替换成可空的类型!

类型擦除

在Java中我们都知道 泛型在运行时是被擦除的!在Kt中也是如此的,在运行时候,我们没办法区别 List<Int>List<String>

但是 kt 中提供了一种有局限的解决方法!通过inlinereified两个关键字,来支持真泛型函数!但是也是有局限的!

// 我们 使用了内联 和真泛型
inline fun <reified T> isInstance(obj: Any?): Boolean {
    if (obj == null) return false
    return obj is T
}

fun main(args: Array<String>) {

    val obj = "kt"
    val isString = isInstance<String>(obj)
    println(isString)

}

这里的泛型可以做如下的事情:

  1. 在类型检查和类型转换中使用 (is , !is , as , as?
  2. 可以获取到 KClass , 通过 T::class可以获取KClass,当然获取了Kt的KClass,也就能相应的获取到 Java 的 Class对象了
  3. 可以作为调用其他泛型函数的,类型实参

不能做以下的事情:

  1. 创建类型参数对应的类的实例
  2. 调用类型的伴生对象的方法
  3. 把类,属性,和非内联的设置成reified

协变逆变

Kt中支持泛型的变型的!可以使用 outin 来定义变型的方式!out表示协变,in表示逆变!

类和类型

什么是类!什么是类型!如果在没有泛型的类中,这很简单,一个类就是代表一种类型!但是在存在泛型的时候,我们就需要用另外的方式思考类型了!每一个含有泛型声明的类!都是一个类型构造器!当传入不同的类型的泛型的时候,会构造成不同的类型!List 接口是一个有一个泛型的类型构造器!List<Int>List<String>他们是通过List泛型构造器构造出来的类型!所以有泛型的类!可以构造出无数种类型!只要他们的泛型不同,就是不同的类型!(没有泛型的类,也能看成是含有0个泛型的类型构造器!)

通过泛型构造的类型,之间的继承关系如何呢?比如List<Any>List<Int>是什么关系呢?如果我们直接定义成,两者之间没有任何的关系(Java就是这么做的),有的时候我们会发现,程序很难描述!

15449622081620.jpg

上图中,我们定义了 feedAll 方法 !它能喂养Animal群,但是我们传入一个 Cat 群的时候,发现编译器是报错的!为什么?因为Herd<Animal>Herd<Cat>之间没有任何的关系,没办法将一个 Herd<Cat>类型的变量传递给Herd<Animal>类型!这很不可思议对吧!所以说我们需要一种工具描述泛型构造器构造出来的类型之间的关系!
这就是我们要说的型变

型变分 协变逆变

  1. 协变:如果 A 是 B 的父类,并且通过泛型构造器 L ,构造的 L<A>L<B> 的父类!那么就说L是协变的!
  2. 逆变:通过泛型构造器L,L<A>L<B> 的子类,就说L是逆变的!

型变的理论只发生在有泛型的类型构造器上!

我们上面的 Herd<Cat> 应该是 Herd<Animal>的子类才是!说我们应该讲 Herd类设置成 协变! class Herd<out T : Animal>

但是我们不能将所有的泛型都设置成out 或者 in,这两者也不是乱用的!在kt中他们只能放在特定的地方!

将一个泛型设置成 outin 会影响这个泛型的使用的位置!被out修饰的泛型只能被放在out位置!被in修饰的只能放在in位置!

15449628978179.jpg

out位置我们指的是生产者位置!我们不能消费它!

in位置我们指的是消费者位置,我们不能生产它!

如果违背了这样的规则,实际上类型是会出现问题的!但是在kt中编译器能帮助你正确的使用inout,用错了会在编译时报错!

out 用在消费者上

举例上面的 Transformer 接口,如果此时 Tout的(协变的)!现有 A,B类,其中类A是类B的父类,记做A <= B!
Transformer 是 协变的!则 Transformer<A> <= Transformer<B>,以下代码:

val a : Transformer<A> 
val b : Transformer<B> = Transformer<B>()

// 根据里氏替换原则,子类可以代替父类!即 b 赋值给 a
a = b 

a.transform(/* 传入的可以是 A 或者A的子类对象 */)

// 但是 最终 transform 是通过多态调用到 b 对象的!b对象的 transform方法只能接受B或者B的子类对象!这样就冲突了!

如果 Transformer 是逆变的呢?

Transformer<B> <= Transformer<A>

val a : Transformer<A> = Transformer<A>()

// 里氏替换原则!
val b : Transformer<B> = a 

b.transform( /* 能传入B或者B的子类 */)

// 方法也是通过多态,最终调用的是 a 对象的 `transform` 方法! 这个方法,只能接受 A 或者 A的子类!我们传入的是B或者B的子类,他们也必然是A的子类,所以没有问题!

// 但是又会出现新的问题!

val res : B = b.transform( /* someObj */)

// 我们调用 b 的 transform 方法!从接口层次看,我们能拿到一个 B对象,或者B对象的子类对象!但是这个方法是最终多态调用a的,a的transform 方法,返回的 A或者A的子类对象!此时我们就是把 A对象赋值给B类型了,违背了里氏替换原则!(父类对象不能代替子类对象!)

从上面的分析可以看得出,参数和返回值是不能用同一种型变的!参数是逆变的,返回值是协变的!所以我们分出了 inout位置,位置上的泛型,不能乱用!

15449485369323.jpg

使用点变型

kt中我们通过在泛型类上使用 intout关键字,声明泛型的型变方式!这种声明型变的方式我们称为 声明点变型,在Java中我们也有型变!例如我们可以使用List<? extends Object> 来接一个 List<String>的对象!这种叫做使用点变型

使用点泛型需要在使用泛型的地方,都要去使用操作符!比较的麻烦!kt这种声明式更加的简单!

在kt中我们也支持使用点泛型,主要的作用是,给那些,没有声明型变方式的类提供的!(Java中的泛型类,都是不型变的,kt支持使用点泛型也是为了兼容性考虑!),你可以给没有声明型变方式的类,在调用点设置inout表示型变!你不可以在使用的地方声明和类中型变方式相反的型变类型!例如kt中 List是协变的 ,你不可以什么一个List<in T>类型!编译器会报错的!但是你可以声明List<out T>类型!不会报错,但是是多此一举的。

如果你在参数中,使用了使用点泛型那么你也会限制这些对象的使用场景!

例如 MutableList 是不型变的:


val list: MutableList<in String> = MutableList<String>()

// 你不能用 String去接!只能用 Any? 去接!因为他是所有类的父类!
val i : String = alis[1]

val list2: MutableList<out String> = MutableList<String>()
// 编译出错!你不能给协变的List,添加任何的东西!因为入参是在 `in` 位置!
list2.add("hello")

我们说上面的 listlist2 不是一个常规的MutableList,它们被称为投影!受限的MutableList

使用类型投影,会导致对象的方法调用受限制!

星(*)投影

如果你不知道关于类型参数的任何信息!那么你可以使用 * 表示他们!

星投影使用在不同的地方,有不同的含义!

  1. Function<*, String> 表示 Function<in Nothing, String>
  2. Function<Int, *> 表示 Function<Int, out Any?>
  3. Function<*, *> 表示 Function<in Nothing, out Any?>

星投影,只能使用在,你对泛型具体类型不感兴趣的地方!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容