概述:在java中泛型的方便和灵活之处,相信广大程序员深有体会,泛型的使用为代码的封装提供了无限可能,好的东西自然要保存下去,在Kotlin中同样提供了泛型的使用,而且扩展了其功能,并简化了使用方式,本篇文章就从自己学习的角度,对java和kotlin中的泛型进行简单的总结,以便于更好的理解泛型的使用,下面开始学习吧,与 Java 类似,Kotlin 中的类也可以有类型参数,这可能是泛型最基本的使用了吧:
class Box<T>(t: T) {
var value = t
}
创建一个类Box声明其接受T类型的参数,此时T为泛型,在使用时再具体指定传入参数的类型,现在来创建对象:
val box: Box<Int> = Box<Int>(1)
这样的创建方式,大家都见过这里不再过多叙述,有一点在前面创建对象时提到过的,就是在kotlin中,若程序能能推断出参数的类型,允许省略参数类型:
val box = Box(1)
上面的简单例子让大家了解下kotlin中泛型的基本使用,下面我们通过与java中的对比进行更细致的学习。
一、协变
Java 中的泛型是不型变的,举个例子:String 是Object的子类,但 List<String> 并不是List<Object> 的子类型,我们进一步的尝试java中哪些情况下的是型变的
分别创建了object 和 strings 的集合,并把string的类型赋值给object此时会报错,这意味着 List<String> 并不是List<Object> 的子类型,把我们调用addAll()把string加入到object中,编译通过,查看addAll的源码:
//创建object的集合
ArrayList< Object> objects = new ArrayList<>();
objects.add(str);
//那创建List<String>
ArrayList< String> strings = new ArrayList<>();
objects=strings;
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
这里参数并不是传入E而是传入? extends E,通配符类型参数 ? extends E 表示此方法接受 E 或者 E 的 一些子类型对象的集合,而不只是 E 自身。 这意味着我们可以安全地从其中(该集合中的元素是 E 的子类的实例)读取 E,但不能写入, 因为我们不知道什么对象符合那个未知的 E 的子类型。 反过来,该限制可以让Collection<String>表示为Collection<? extends Object>的子类型。 简而言之,带 extends 限定(上界)的通配符类型使得类型是协变的(covariant)。
现在我们修改上面的object集合:
ArrayList<? extends Object> objects = new ArrayList<>();
objects=strings; 编译通过 OK
此时在向其添加集合就会编译错误,即只允许读取其中的object属性,不允许写入数据:
//此时以下写入的代码报错:
objects.addAll(integers);
objects.addAll(strings);
objects.add(str);
//表示 无法添加 集合,
可以读取 Object o = objects.get(0);
因为从中可以读取到object的对象,但不知道究竟是object的那个子类故无法添加对象,以上是利用java中已经存在的集合,对型变的通配符的简单介绍,下面我们看看泛型在创建类的时候的使用:
public class TypeClass<T > {
}
接下来我们同样创建对象:
TypeClass<String> stringTypeClass = new TypeClass<>();
TypeClass<Object> objectTypeClass = stringTypeClass;//报错
TypeClass<String> stringTypeClass = new TypeClass<>();
TypeClass<? extends String> objectTypeClass = stringTypeClass;//通过
Kotlin 中的协变:
声明处协变:
为了修正这一点,我们必须声明对象的类型为 <? extends Object>,这是毫无意义的,因为我们可以像以前一样在该对象上调用所有相同的方法,所以更复杂的类型并没有带来价值。但编译器并不知道。在 Kotlin 中,有一种方法向编译器解释这种情况。这称为声明处型变:我们可以标注 类的类型参数 T 来确保它仅从 <T> 成员中返回(生产),并从不被消费。 为此,我们提供 out 修饰符:class TypeClass<out T> {
}
var string = TypeClass<String>()
var any : TypeClass<Any> = string
标记为out 的泛型T,在创建的对象为Any时,可以接受其子类的类型。
另外除了
out
,Kotlin 又补充了一个型变注释:
in
。它使得一个类型参数
逆变
:只可以被消费而不可以
被生产: