Kotlin-15.泛型(generics)

官方文档: http://kotlinlang.org/docs/reference/generics.html

1.泛型(generics)

与Java类似,Kotlin的类也有类型参数(泛型):        
    class Box<T>(t: T) {
        var value = t
    }
    
一般情况,使用泛型实例,需要类型参数:
    val box: Box<Int> = Box<Int>(1)

如果类型参数可推断出来,可省略类型参数:
    val box = Box(1) // 1是Int,编译器可推断出Box<Int>

2.型变(Variance)

Java泛型中最棘手部分就是通配符类型(初学实在头晕),但kotlin没有!
所以Kotlin通过型变(Variance)弥补:
    声明处型变(declaration-site variance)
    类型投影(type projections)

为什么Java泛型需要通配符类型?
    在Effective Java解释了该问题—第28条:利用有限制通配符来提升API的灵活性。 
    Java泛型是不型变的,意味着List<String>不是List<Object>子类型! 
    如果List是型变的,如下代码编译正常,但运行时出现异常:
        // Java
        List<String> strs = new ArrayList<String>();
        List<Object> objs = strs; //错误,Java禁止型变!
        objs.add(1);
        String s = strs.get(0); //运行出现异常ClassCastException:无法将整数转为字符串!

    因此,Java禁止型变以保证运行时的安全,但这样会有一些影响,
    例如,假设Collection.addAll()参数如下:
        // Java
        interface Collection<E> …… {
            void addAll(Collection<E> items);
        } 
        void copyAll(Collection<Object> to, Collection<String> from) {
            //addAll不能编译,Collection<String>不是Collection<Object>子类型
            to.addAll(from);      
        }
    这就是为什么Collection.addAll()实际参数如下:
        // Java
        interface Collection<E> …… {
            //通配符<? extends E>表示包括E在内的所有子类,称为协变(covariant)
            //通配符<? super E>表示包括E在内的所有父类,称为逆变(contravariance)
            void addAll(Collection<? extends E> items);
        }
        void copyAll(Collection<Object> to, Collection<String> from) {
            //<? extends E>可以让Collection<String>是Collection<? extends Object>子类型
            to.addAll(from);
        }

    <? extends E>协变(covariant): 表示包括E在内的所有子类,泛型对象只能读取,称为生产者
    <? super E>逆变(contravariance): 表示包括E在内的所有父类,泛型对象只能写入,称为消费者
    助记符:Producer-extends, Consumer-super

3.声明处型变(Declaration-site variance)

Java泛型的一个例子:
    interface Source<T> {
        //只有生产者方法,没有消费者方法
        T nextT();
    }
    void demo(Source<String> strs) {
        //Source<T>没有消费者方法,型变是安全的,但是Java并不知道,所以仍然禁止!
        //需要声明类型为Source<? extends Object>,这是毫无意义的,更复杂类型并没有带来价值!            
        Source<Object> objects = strs; //错误:在Java中不允许型变
    }

1.out修饰符
在Kotlin中,可用out修饰类型参数T,确保T只能输出(生产),不被消费!
out修饰符称为型变注解(variance annotation),使类型参数协变(covariant)!    
    abstract class Source<out T> {
        abstract fun nextT(): T
    }
    fun demo(strs: Source<String>) {
        val objects: Source<Any> = strs //可以型变,因为T是out      
    }

2.in修饰符
用in修饰类型参数T,确保T只能被消费,不能输出(生产),使类型参数逆变(contravariance)!
    abstract class Comparable<in T> {
        abstract fun compareTo(other: T): Int
    }
    fun demo(x: Comparable<Number>) {
        //1.0拥有Double类,是Number的子类
        x.compareTo(1.0)    

        //Double是Number的子类,父类Number可以被Double消费
        val y: Comparable<Double> = x
    }

助记符:消费者-输入in, 生产者-输出out
由于in/out在类型参数声明处,所以称为声明处型变(Declaration-site variance)

4.使用处型变(Use-site variance)/类型投影(Type projections)

类型参数T既不是协变,也不是逆变(T既生产out,又消费in):
    class Array<T>(val size: Int) {
        fun get(index: Int): T {...} //生产out
        fun set(index: Int, value: T) {...} //消费in
    }     
    fun copy(from: Array<Any>, to: Array<Any>) {
        assert(from.size == to.size)
        for (i in from.indices)
            to[i] = from[i]
    }
    val ints: Array<Int> = arrayOf(1, 2, 3)
    val anys = Array<Any>(3) { "" } 
    copy(ints, anys) //错误:期望(Array<Any>, Array<Any>)

1.out,确保from中的Any只生产输出,不被消费,对应于Java的Array<? extends Object>:
    fun copy(from: Array<out Any>, to: Array<Any>) {
        ...
    }

2.in,确保dest中的String只能被消费,不生产输出,对应于Java的Array<? super String>
    fun fill(dest: Array<in String>, value: String) {
        ...
    }    

5.星投影<*>(Star-projections)

如果对类型参数一无所知,可用星投影:
    1.对于Foo<out T>,T是一个具有上界TUpper的协变类型参数,Foo<*>等价于Foo<out TUpper>, 
    当T未知时,可以安全地从Foo<*>读取TUpper的值

    2.对于Foo<in T>,T是一个逆变类型参数,Foo<*>等价于Foo<in Nothing>,
    当T未知时,没有什么方式可以安全写入Foo<*>

    3.对于Foo<T>,T是一个具有上界TUpper的不型变类型参数,
    Foo<*>在读取值时等价于Foo<out TUpper>,在写入值时等价于Foo<in Nothing>

如果有多个类型参数,则每个类型参数都可单独投影:
    interface Function <in T, out U>
    Function<*, String>   表示Function<in Nothing, String>
    Function<Int, *>      表示Function<Int, out Any?>
    Function<*, *>        表示Function<in Nothing, out Any?>
注意:星投影非常像Java的原始类型,但是安全!

6.泛型函数

和java类似, kotling不仅类有泛型,函数也有泛型:
    //普通函数
    fun <T> singletonList(item: T): List<T> {
        // ……
    }

    //扩展函数
    fun <T> T.basicToString() : String {  
        // ……
    }

    //调用泛型函数,在函数名后指定类型参数
    val l = singletonList<Int>(1)

7.泛型约束

最常见的约束类型是,与Java的<? extends T>对应的上界:
    //<T : Comparable<T>>冒号之后指定类型上界,只有Comparable<T>子类型可以替代T
    fun <T : Comparable<T>> sort(list: List<T>) {
    }
    sort(listOf(1, 2, 3)) //Int是Comparable<Int>子类型
    sort(listOf(HashMap<Int, String>())) //错误: HashMap<Int, String>不是Comparable<HashMap<Int, String>>子类型

默认上界是Any?,<:上界>中只能指定一个上界,如果同一类型参数需要多个上界,需要一个单独的where子句:
    fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>
        where T : Comparable, T : Cloneable {
        return list.filter { it > threshold }.map { it.clone() }
    }   

简书:http://www.jianshu.com/p/e9e743baa77a
CSDN博客: http://blog.csdn.net/qq_32115439/article/details/73656998
GitHub博客:http://lioil.win/2017/06/23/Kotlin-generics.html
Coding博客:http://c.lioil.win/2017/06/23/Kotlin-generics.html

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

推荐阅读更多精彩内容