Kotlin 第十二章:泛型
来源:互联网 发布:如何解决软件危机 编辑:程序博客网 时间:2024/06/15 11:13
Kotlin 第十二章:泛型
泛型,即“参数化类型”,顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
泛型
在 Java 中,泛型的使用时比较广泛的,比如:
class Box<T> { private T var ; }// 使用Box<String> box = new Box<String>();
在 Kotlin 中也有泛型的概念,比如:
class Box<T>(t: T) { var value = t}// 使用val box: Box<Int> = Box<Int>(1)
但如果类型有可能是推断的,比如来自构造函数的参数或者通过其它的一些方式,一个可以忽略类型的参数:
val box = Box(1)//1是 Int 型,因此编译器会推导出我们调用的是 Box<Int>
而在Java中是不能这样做。
变化
在这里我们先来了解下 Java 泛型中的通配符,在 Effective Java 中 Item 28 中写到: Use bounded wildcards to increase API flexibility
,意思是使用通配符为了提高 API 的使用灵活性。
Java 中使用 ?
来表示通配符:
public static <T extends Comparable<? super T>> void sort(List<T> list) { Object[] array = list.toArray(); Arrays.sort(array); int i = 0; ListIterator<T> it = list.listIterator(); while (it.hasNext()) { it.next(); it.set((T) array[i++]); }}
在 Java 中,泛型是不可变的,如 List<String>
不是 List<Object>
的子类:
List<String> strs = new ArrayList<String>();List<Object> objs = strs; // 这是错误的,类型不匹配
如果上面的操作是正确的,那么会在使用时造成类型不匹配的问题:
objs.add(1); // 添加一个int类型到String的List里面String s = strs.get(0); // 会报ClassCastException: Cannot cast Integer to String的错误
所以 Java 的泛型会被设计成不可变的类型,就是为了确保运行时类型安全,但是这样同样会带来一些影响。
举个例子,定义一个泛型接口 Collection
,里面有 addAll()
方法:
interface Collection<E> ... { void addAll(Collection<E> items);}
由于 Java 的泛型是不可变的,所以下面的代码是做不到的:
void copyAll(Collection<Object> to, Collection<String> from) { to.addAll(from); // 这是错误的,Collection<String>不是Collection<Object>的子类}
为了解决上面的问题,Java 中使用了类型通配符方式,如 ? extends T
表示 T
及 T
的子类参数都可以使用,所以 Collection
的 addAll()
方法是这样写的:
interface Collection<E> ... { void addAll(Collection<? extends E> items);}
同样的原理,在上面的代码可以改成这样:
List<String> strs = new ArrayList<String>();strs.add("0");objs.add("1"); List<? extends Object> objs = strs; // OK
通配符上界
<? extends T>
(T
表示通配符的上界),表示可以接收 T
以及 T
的子类参数,也就是说可以安全的读取到 T
的实例,事实上所有的集合元素都是 T
的子类的实例,但不能向其添加元素,因为没法确定添加的实例类型跟定义的类型是否匹配,举个栗子:
List<String> strs = new ArrayList<String>();strs.add("0");strs.add("1");List<? extends Object> objs = strs;// 上面说过这样是可以objs.get(0); // 可以获取
// 但是再添加一个int类型的话objs.add(1); // 报错
// 再添加一个String类型objs.add("1"); // 同样会报错
上面的例子说明了 objs
可以读取值,但是再往 objs
里面添加值的时候,就会出错,没法确定添加的实例类型跟定义的类型是否匹配。
这种 wildcard 是通过继承一个范围类 (extends-bound),也就是通配符上界 (upper bound) 来实现类型协变。
通配符下界
那么有通配符上界 <? extends T>
,自然就会有下界,<? super T>
,其中 T
就表示通配符的下界。
举个栗子:Collection<? super String>
是 Collection<String>
的父类型,所以可以直接 add
和 set
,但是 get
的时候获取到的类型是 Object
而不是 String
类型。
List<String> strs = new ArrayList<String>();strs.add("0");strs.add("1");List<? super String> objs = strs;objs.add("1");objs.set(0, "2");Object s = objs.get(0);
在 Kotlin 中,并没有上面的机制,而是通过 Declaration-site variance
和 Type projections
来执行的。
泛型函数
Kotlin 同样支持泛型函数:
fun <T> singletonList(item: T): List<T> { // ...}
fun <T> T.basicToString() : String { // extension function // ...}
使用的时候,在函数名称后面指定具体的类型参数:
val l = singletonList<Int>(1)
声明位置变化
声明位置变异:通过将参数 T
注解成只能作为返回值,不能作为传入参数;使用 out
关键字标识。
首先我们来看一下,在 Java 中,
interface Source<T> { public T nextT();}public void demo(Source<String> strs){ Source<Object> objs = strs; // 在Java中是不允许的 // 正确方式为 // Source<? extends Object> objs = strs;}
在 Kotlin 中,使用声明位置变异来解决这种问题:
abstract class Source<out T> { // 使用out的话,T只能作为返回值 abstract fun nextT(): T // 不能作为传入参数,下面会报错 // abstract fun add(value: T)}fun demo(strs: Source<String>) { val objects: Source<Any> = strs}
有 out
就有 in
,in
与 out
互补,它使类型参数逆变 contravariant
,只能作为传入参数,不能作为返回值:
abstract class Source<in T> { // 使用in的话,只能作为传入参数,不能作为返回值 // abstract fun nextT(): T abstract fun add(value: T)}fun demo(strs: Source<Number>) { val objects: Source<Double> = strs // Double是Number的子类型}
总结一下,当一个泛型类 C
,包含 out
关键字的时候,等同于 Java 的 extends
,将类 C
称为 T
的协变类,T
只能作为该类中函数的返回类型,不能作为参数传递进来,也可以称类 C
为 T
的生产者(Producer)。
同理,当包含 in
关键字的时候,等同于 Java 的 super
,将类 C
称为 T
的逆变类,T
只能作为该类中函数的参数传递进来,不能作为返回类型,也可以称类 C
为 T
的消费者(Consumer)。
可以将上面两段话总结成:
Consumer in, Producer out!
fun copy(from: Array<out String>, to: Array<in String>) { assert(from.size == to.size) for (i in from.indices) to[i] = from[i]}// 等同于public void copy(List<? extends String> from, List<? super String> to) { ... }
后记
Kotlin 在后面的学习中是越学习越到困难和吃力,还好找到了大神的简书一直做参考给了不少帮助,这一篇学习文章虽然是写完了,但还是感觉懵懵懂懂,希望以后能有进步。希望各位看官能够不吝啬提出宝贵意见。
参考
Kotlin中文文档
叫我旺仔的简书
- Kotlin 第十二章:泛型
- 《Kotlin 程序设计》第十二章 Kotlin的多线程:协程(Coroutines)
- Kotlin-泛型
- Kotlin 泛型
- Kotlin-泛型
- 第十二章
- 第十二章
- 第十二章
- 第十二章
- 第十二章
- 第十二章..
- 第十二章
- 第十二章
- 第十二章
- 第十二章
- Kotlin第二章:Kotlin 的基本语法
- 《Kotlin 程序设计》第三章 Kotlin 类型系统
- 《Kotlin 程序设计》第四章 Kotlin 语法基础
- IDEA17配置SpringMVC及HelloWorld例子
- 34.Java基础语法
- ecos vector.S 分析I: 主干部分
- div中文字超长的换行神器:word-break:break-all;
- Centos下编译安装fileinfo扩展
- Kotlin 第十二章:泛型
- opencv录制视频并保存视频
- ecos kernel 分析
- css 多行隐藏
- 1031. Hello World for U (20)
- 索引超出了数组界限(Microsoft.SqlServer.Smo) SSMS连接远程SQL Server服务器是很方便的。 昨天我用SQL Server 2008 SSMS连接SQL Server
- 在mac环境下Qt的布局控件重叠
- 有线/无线网卡驱动原理简介
- switch芯片上的QoS,VLAN介绍