美文网首页KotlinKotlin编程之旅
Kotlin第七讲--泛型在Java和Kotlin上的横向比较

Kotlin第七讲--泛型在Java和Kotlin上的横向比较

作者: sugaryaruan | 来源:发表于2018-07-16 16:24 被阅读1次

    阅读本文前,如果对Java泛型不够了解,不妨先阅读我之前写的两篇说Java泛型的文章

    重拾Java泛型 上篇

    重拾Java泛型 下篇

    语法比较

    泛型方法

    Java

    public static<T extends Comparable<? super T>> T max(List<? extends T> list){
            Iterator<? extends T> iterator = list.iterator();
            T result = iterator.next();
            while (iterator.hasNext()){
                T t = iterator.next();
                if(t.compareTo(result) > 0){
                    result = t;
                }
            }
            return result;
        }
    

    Kotlin

    fun <T : Comparable<in T>> max(list: List<out T>): T {
        val iterator = list.iterator()
        var result: T = iterator.next()
        while (iterator.hasNext()) {
            val t = iterator.next()
            if (t.compareTo(result) > 0) {
                result = t
            }
        }
        return result
    }
    

    查看Kotlin里的Comparable类和List类源码,Comparable类定义<in T>,List类定义<out T>,所以可以不必重复表明Comparable的逆变能力和List的协变能力。

    fun <T : Comparable<T>> max(list: List<T>): T {
        val iterator = list.iterator()
        var result: T = iterator.next()
        while (iterator.hasNext()) {
            val t = iterator.next()
            if (t.compareTo(result) > 0) {
                result = t
            }
        }
        return result
    }
    

    不管是泛型类还是泛型方法,泛型的使用分为两个步骤

    1. 第一步,声明泛型参数
    2. 第二步,才是使用泛型参数

    上面的泛型方法,以Java泛型方法为例,泛型声明部分是:

    <T extends Comparable<? super T>>
    

    引入泛型类型名称为T,这个数据类型的关系是实现Comparable的类。

    使用泛型的部分如下:

    List<? extends T>
    

    入参的集合具有对类型T的协变能力。

    所以,有些书或者网上资料,会出现两个概念:声明点变型,使用点变型。这个概念说的就是这件事

    再来看下in位置,和out位置的概念

    in_out位置图.png

    所以,类型参数T上的关键字out有两方面含义:

    1. 具有协变的能力
    2. T只能用在out位置

    我们再看一个有趣的例子

    我有一个方法,入参的一个集合,我希望入参集合元素是EditText的子类的集合,如何构建满足这个关系的方法呢?至少有下面的两种方式:

    fun test2(list: MutableList<out EditText>){
    
    }
    
    fun <T: EditText>test3(list: MutableList<T>){
    
    }
    

    上面test2, test3方法都能实现预期的效果。不同的是,test2使用协变来实现,也意味着只能读不能写操作,test2不是泛型方法。test3方法通过在声明表明泛型参数的类型关系来实现,可读可写,它是泛型方法。

    对应的Java代码如下

    private void test2(List<? extends EditText> list){
    
    }
    
    private <T extends EditText> void test3(List<T> list){
        
    }
    

    实现泛型参数多约束

    Java

    private <T extends Serializable & CharSequence> void ensureTrailingPeriod(T seq){
            
    }
    
    

    kotlin

    fun <T> ensureTrailingPeriod(seq: T) where T: Serializable, T: CharSequence{
        
    }
    
    

    这是Java语法上很大的不同,Kotlin要实现多约束,使用到where关键词,这是全新的表示法。

    实化类型参数

    来看一段Java代码:

    private <T> void test(T t){
        if(t instanceof String){
            String s = (String)t;
    
        }else if(t instanceof Integer){
            Integer i = (Integer)t;
        }
    }
    

    入参是泛型的,通过instanceof来判断泛型具体是哪个类型。再看一段代码

    private <T> void test2(Object o){
        //编译不通过
        if(o instanceof T){
    
        }
    }
    

    泛型作为参数类型时,编译不通过。如果要完成上述的逻辑,要怎么实现?可以通过class近似等效实现

    class ClassJudge<T>{
        Class<T> kind;
    
        public ClassJudge(Class<T> kind) {
            this.kind = kind;
        }
    
        public boolean isInstance(Object o){
            return kind.isInstance(o);
        }
    }
    

    调用

        Integer a = 12;
        boolean instance = new ClassJudge<String>(String.class).isInstance(a);
        System.out.println("instance = " + instance);
    

    Kotlin在语言层面作出了支持,对上述Java代码test2方法,kotlin代码如下:

    inline fun<reified T> test2(any: Any){
        if(any is T){
            
        }
    }
    

    因此需要具备两个条件:

    1. 是内联函数 inline
    2. 关键词 reified

    集合协变

    在Kotlin里,非空类型是可空类型的子类型。

    Java实现的协变

    List<? extends Number> list = new ArrayList<Integer>();
    

    Kotlin实现的协变

    val list: MutableList<out Number> = arrayListOf<Int>()
    

    集合逆变

    Java实现的逆变

    List<? super Integer> list = new ArrayList<Number>();
    

    对应的Kotlin逆变的实现:

    val list: MutableList<in Int> = arrayListOf<Number>()
    

    变型(协变和逆变)涉及到集合元素,集合类。协变讲的是两个集合的元素是子类关系,这两个集合也是子类关系,有了子类关系,就可以用多态表示。逆变的关系是反过来的,逆变说得是,两个集合的元素是父类关系,这两个集合却能成为子类关系。

    由此可见,在泛型里,extends 不全等于 :。extends代表子类型关系和协变,而Kotlin的 :只代表子类型关系。

    <out T> = <? extends T>
    <in T> = <? super T>
    
    

    星投影和Java无限制通配符?差异

    星投影如何使用

    <*> = <out Any?>
    <*> = <in nothing>
    

    投影一词,顾名思义,是对Any?的投影,获得了Any?部分的能力---Any?协变的能力,即失去写操作,只能读操作。

    fun printFirst(list: List<*>){
        if(list.isNotEmpty()){
            println(list.first())
        }
    }
    

    场景二:泛型类存储

    所有不安全的逻辑都被隐藏在类的主体中,通过把这些逻辑局部化到一个分开的位置,预防了误用,保证了它不会被错误地使用。

    其他

    非空性与泛型

    Java和Kotlin默认,泛型参数都是可空的,以Kotlin为例

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

    泛型参数是可空类型,可以理解为默认实现<T: Any?>,因此要实现非空类型,只需<T: Any>,如下

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

    所以,泛型<T> 和<T: Any>是有区别的,前者是可空类型,后者是非空类型,确切说,<T>和<T:Any?>等效

    小结

    Kotlin和Java的差异,体现在语法和功能两个方面

    语法上

    1. Kotlin使用:替代 extends;
    2. 用星投影 * 替代?
    3. 用out, in来体现协变和逆变;
    4. 泛型类型多约束条件,Kotlin使用where关键词,而不用Java里&表示

    功能上:

    1. 新增了实化参数类型

    参考资料

    仔细说说Java中的泛型

    Java 泛型 <? super T> 中 super 怎么 理解?与 extends 有何不同?

    Java 泛型进阶

    Java为什么要添加运行时获取泛型的方法

    Kotlin实战

    欢迎关注CodeThings

    相关文章

      网友评论

        本文标题:Kotlin第七讲--泛型在Java和Kotlin上的横向比较

        本文链接:https://www.haomeiwen.com/subject/nikbpftx.html