标签: Kotlin
本文声明:
本文由Coder-pig编写,想了解其他内容,可见【Coder-Pig的猪栏】
尊重作者劳动成果,未经本人授权,禁止转载!违者必究!
《Kotlin搞起来》系列目录地址:http://blog.csdn.net/coder_pig/article/details/72851862
PS:本来是想把泛型加到上一节的类与对象中的,后来发现Kotlin文档中对泛型的
介绍令人难以理解,而且很多小伙伴对Java中的泛型也是一知半解,所以还是把
泛型这一章抽取出来,顺便捋一捋一些概念性的东西。
Java泛型的引入
问题引入:
集合框架能存储各种对象,但是取出对象的时候可能出现类型不兼容的异常,比如:
往集合中插入String和Integer的对象,取出的对象可能是String或Integer类型的,
如果不用instanceof()判断对象类型,再用对应类型的变量存储的话,就可能会发
生ClassCastException(类型转换异常)
疑问:
每次取元素都要进行判断,显得非常繁琐,能不能在创建的时候就指定元素类型?
让编译器负责检查添加元素的合法性?
回答:
当然可以,jdk 1.5引入的泛型(Generice)能帮我们解决问题,将确定不变的类型参数化,
从而保证集合中存储的元素类型都是同一种,而且所有的强转都是自动与隐式的,
从而提高了代码的重用率。
Java中泛型的使用
泛型类 :
泛型接口:
泛型方法:
注意事项:
- 1.泛型的类型参数只能是类类型,不能是简单数据类型(int那些);
- 2.泛型可以有多个泛型参数;
- 3.有时我们想让集合存取的元素为某个类子类或者父类而非特定一个类,可以使用
extends和super进行约束,这个又称为有界类型,比如有个父类是People,然后他有
几个子类,Student,Worker,Police,想允许People和他的三个子类都运行放到集合中,
直接写成<T extends People>就可以了,这个称上界约束,而super则称下界约束。
Java假泛型实现原理
和C#中的泛型不同,Java和Kotlin中的泛型都是假泛型,实现原理就是:类型擦除(Type Erasure),
Java编译器在生成Java字节码种是不包含泛型中的类型信息的,使用泛型的时候加上的类型参数,
会在编译器编译的时候去掉,然后用其限定类型替换!
比如下述代码,获取到的类类型都是一样的(ArrayList)
因为编译时会用限定类型替换,所以如果这里我们用反射往一个Integer类型的List插入
String类型的参数是不会报错的:
PS:关于类型擦除更多内容可见:http://blog.csdn.net/lonelyroamer/article/details/7868820
Java泛型通配符的引入
先来看一段代码:
我们都知道在Java中Object类是所有类的默认父类,按理来说应该是可以转型的。
然而编译器却报错了:
这里我们假设没报错,这个时候如果我们往list2里添加一个整数:
假设也没报错,但是运行的时候肯定是会报ClassCastException错误的!
Java中的泛型是不型变的,意味着 List<String>并不是List<Object>的子类型,
Java以此方式来保证运行时的安全。为了解决这个问题,Java中又引入了泛型通配符:?
<? extends E>
表示该方法只接受E或者E的子类,但不知道是哪个具体的子类型,所以只能读不能写
从而使得类型是协变的,又叫上界限定通配符。看到Collection接口里的addAll方法就了解了:
<? super E>
对应的逆变则是只能接受E或者E的父类,因为无法判断读取到的类型,所以只能写不能读,
又叫下界限定通配符。
<?>
最后还有个无限定通配符,类型完全位置,此时只能读不能写,这个叫不变。
Kotlin中的型变
基本的泛型用法比较简单,和Java中的基本类似,难点还是型变,Kotlin中没有通配符类型,
但有其他两个东西:声明处型变(declaration-site variance)与 类型投影(type projections)。
声明处型变
在Java里,如果你写这样一段代码,编译器是不会通过的:
按照我们的理解来说,上面的代码应该是没问题的,但是编译器不知道,仍然禁止这样的操作,
我们可以声明通配符对象类型为:
使用了更复杂的类型并没有任何意义,依旧可以调用对象的所有相同的方法,编译器依旧不知道。
Kotlin中,有一种方法向编译器解释这种情况,称为声明处型变。使用到的修饰符是:in 和 out
out:协变,只能用作输出,可以作为返回值类型,但无法作为入参类型
输出结果:
in:逆变,只能用作输入,可以作为如残类型,但无法作为返回值类型
类型投影
使用处型变,有些类实际上不能限制为只返回T,该类在T上既不能是协变的也不能是逆变的,
然后根据不同的方法,你可以使用in 或 out进行限制,比如:
fun copy(from: Array<out Any>, to: Array<Any>) { /* …… */ }
fun fill(dest: Array<in String>, value: String) { /* …… */ }
*投影
和Java中的?通配符类似,Kotlin可以根据 * 所指代的泛型参数进行相应的映射,官方解释如下:
- 对于 Foo <out T>,其中 T 是一个具有上界 TUpper 的协变类型参数,Foo < * > 等价于 Foo <out TUpper>。这意味着当 T 未知时,你可以安全地从 Foo <*> 读取 TUpper 的值。
- 对于 Foo <in T>,其中 T 是一个逆变类型参数,Foo < * > 等价于 Foo <in Nothing>。
这意味着当 T 未知时,没有什么可以以安全的方式写入 Foo < * >。- 对于 Foo <T>,其中 T 是一个具有上界 TUpper 的不型变类型参数,Foo< * > 对于读取值时等价于
Foo<out TUpper> 而对于写值时等价于 Foo<in Nothing>。
注意事项:
- 不允许作为函数和变量的类型的泛型参数!
- 不允许作为函数和变量的类型的泛型参数!
- 不能直接作为父类的泛型参数传入!
PS:关于Kotlin中泛型最后的几点我还是有点模糊的,实际开发没用过,体会不够深刻,等以后
用到了有所收获后再来对这部分重新编辑~
本文参考文献: