Kotlin 类与对象 —— 泛型 | Ohmer's Blog
与java一样,Kotlin也提供泛型,为类型安全提供保证,消除类型强转的烦恼。
泛型定义
好吧,如果只是简单声明一个泛型,和Java没有什么大的区别,你可以这样声明:
1 2 3 | class Box<T>(t: T) { var value = t } |
然后可以这样使用
1 2 3 4 5 | val box: Box<Int> = Box<Int>(1) // 或者 val box = Box(1) // 编译器会进行类型推断 |
泛型约束
和类的继承一样,Kotlin中使用:代替extends对泛型的的类型上限进行约束。
1 | class SwipeRefreshableView<T : View>{} |
不过这里你可以进行多个类型的上限约束:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class SwipeRefreshableView<T> where T : View, T : Refreshable { } // 或者 fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T> where T : Comparable, T : Cloneable { return list.filter { it > threshold }.map { it.clone() } } |
到这里,对于之前用过泛型的同学来说都没有什么难度。so,kotlin还有什么java里没有的东西吗?
in和out
Kotlin中引入两个新的泛型修饰符in和out,要解释这两个关键字的用法,我们先从另外两个概念说起‘covariant(协变性)’和‘contravariance(逆变性)’(不知道的可以参考)。我们都知道在java中List不是协变的,而Array是协变的:
1 2 3 | Integer[] intArray = new Integer[10]; Number[] numberArray = intArray; numberArray[0] = 1.0f; |
在上面的代码中,Integer[]被认为是Number[]的子类型,所以可以将intArray赋值给numberArray,但是在随后的代码,我们将1.0f赋给numberArray[0],因为在这里看来,将一个浮点型赋给一个Number对象不会有什么问题。最后悲剧发生了,当执行时,程序crash了。
但是当你使用泛型的的时候:
1 2 | List<String> strs = new ArrayList<>(); List<Object> objs = strs; // error, compiler complain |
List<String>并不是List<Object>的子类型,于是编译器告诉你,不能直接赋值。或许你会说我们可以使用通配符? extends T让它变得协变。
1 2 3 4 5 | List<String> strs = new ArrayList<String>(); strs.add("0"); strs.add("1"); List<? extends Object> objs = strs; //编译通过 |
List<String>是List<? extends Object>的子类,所以上面的代码的确能够编译运行,但是当你尝试为objs添加内容时:
1 2 3 4 5 6 7 | //然后添加一个int型试试 objs.add(1); // error, compiler complain // 编译器编译出错 // 现在再添加一个String objs.add("1"); // error, compiler complain // 编译出错 |
对于objs并不会因为objs = strs;的赋值,而将objs的泛型类型转化为String类型,所以在不能判断objs的泛型类型的情况下,往objs添加任何类型的对象都是不被允许的。但是我们明确知道objs的所有类型上限(upper bound),于是我们可以通过objs.get(0)获取Object的对象。
小结一下,我们可以用通配符? extends T让泛型类变得协变,但是对于具体泛型类型的对象我们不能赋值,只能获取。于是在下面的假设中java就可以这么写:
1 2 3 4 5 6 7 8 9 10 | interface Source<T> { public T getT(); public void setT(T t); } public void copy(Source<String> strs){ Source<? extends Object> objs = strs; objs.setT("a"); // error, compiler complain String str = (String) objs.getT(); } |
在Kotlin中就可以这么写:
1 2 3 4 5 6 7 8 9 10 | abstract class Source<T> { abstract fun getT(): T abstract fun setT(t: T) } fun copyT(strs: Source<String>){ val objs: Source<out Any?> = strs; objs.setT("a") // error, compiler complain objs.getT() } |
上面的out Any?可以用*代替。
如果我们可以确定Source这个类不会有abstract fun setT(t: T)类似的操作,我们可以这样写:
1 2 3 4 5 6 7 8 9 10 11 | abstract class Source<out T> { abstract fun getT(): T // 如果下面出现会编译不过 // abstract fun setT(t: T) // error, compiler complain } fun copyT(strs: Source<String>){ val objs: Source<Any> = strs; objs.setT("a") // error, compiler complain objs.getT() } |
小结一下,在定义泛型类C<T>时,当我们在泛型类型T前面添加out,C为T的协变类。在该类的作用域内,类型T只能作为该类中函数的返回类型,不能作为参数传递进来,这时也称做C为T的生产者(Producer)。
以此类推,在定义泛型类C<T>时,当我们在泛型类型T前面添加in,C为T的逆变类。在该类的作用域内,类型T只能作为该类中函数的参数传递进来,不能作为返回类型,这时也称做C为T的消费者(Consumer)。
类似于java中的PECS(Producer Extends,Consumer Super),我们可以总结出:‘Consumer in, Producer out’。
如果在泛型类型使用测,在对应泛型的具体类型前面使用out,则等同于使用java中的extends字段,in则等同于super。
1 2 3 4 5 6 7 8 9 | 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) { ... } |
PS: 这里Array 与 List 不是对等关系。