基本使用
1.泛型接口
interface Generator<T>{
fun next():T
}
val gen = object :Generator<Int>{
override fun next(): Int {
return 1
}
}
println("result: ${gen.next()} ")
这里要用object关键字声明Generator实现类
2.泛型类
class Container<K,V>(val key:K,val value:V){
override fun toString(): String {
return "Container ${key} ${value}"
}
}
val container = Container<Int,String>(1,"ABC")
println(container)
多个泛型使用","隔开,因为kotlin可以推断类型,声明类的时候,泛型也可以省略
val container = Container(1,"ABC")
println(container)
3.泛型函数
fun <T> console(t:T){
println(t)
}
< T>是类型形参声明
类型擦除
JVM上的泛型一般是通过类型擦除实现的,就是说泛型类实例的类型实参在运行时是不保留的 。创建 一个 List<String>并将一堆字符串放到其中,在运行时只能看到它是一个 List。因为类型实参没有被存储下来 ,你不能检查它们 。 例如,你不能判断一个列表是一个包含字符串的列表还是包含其他对象的列表。
类型擦除的好处是:应用程序使用的内存总量较小,因为要保存在内存中的类型信息更少。
在Kotlin,函数声明成 inline并且用 reified 标记类型参数,reified声明了类型参数不会在运行时被擦除。
比如:
fun <T> printIfTypeMatch(item: Any) {
if (item is T) { // IDE 会提示错误,Cannot check for instance of erased type: T
println(item)
}
}
加上reified
inline fun <reified T> printIfTypeMatch(item: Any) {
if (item is T) { // 这里不会提示错误
println(item)
}
}
其他情况:
☕️
TextView textView = new Button(context);
// 这是多态
List<Button> buttons = new ArrayList<Button>();
List<TextView> textViews = buttons;
//多态用在这里会报错 incompatible types: List<Button> cannot be converted to List<TextView>
这是因为 Java 的泛型本身具有「不可变性 Invariance」,Java 里面认为 List 和 List 类型并不一致,也就是说,子类的泛型(List)不属于泛型(List)的子类,可以用数组实现这样的转换,数组在编译时是不会被擦除的
协变与逆变
一个泛型类一一例如 , MutableList一一如果对于任意两种类型 A 和 B,MutableList<A>既不是 MutableList<B > 的子类型也不是它的超类型,它就被称为在该类型参数上是不变型的。如果 A 是 B 的子类型,那么 List < A>就是 List<B>的子类型。这样的类或者接口被称为协变的 。逆变就是,如果 B 是 A 的子类型,那么 Consumer<A>就是 Consumer<B>的子类型。
out in
先看java的extends 和super,
List<Button> buttons = new ArrayList<Button>();
List<? extends TextView> textViews = buttons;
? extends 叫做「上界通配符」,可以使 Java 泛型具有「协变性 Covariance」,extends 限制了泛型类型的父类型,所以叫上界
List<? extends TextView> textViews = new ArrayList<Button>();
TextView textView = textViews.get(0); // get 可以
textViews.add(textView);//add 会报错,no suitable method found for add(TextView)
因为有? extends TextView 的限制条件,所以 get 出来的对象,肯定是 TextView 的子类型,List<? extends TextView> 由于类型未知,它可能是 List< Button>,也可能是 List< TextView>,所以没办法确定类型,没办法add。只能够向外提供数据被消费,「生产者 Producer」。
List<? super Button> buttons = new ArrayList<TextView>();
? super 叫做「下界通配符」,可以使 Java 泛型具有「逆变性 Contravariance],限制了通配符 ? 的子类型,所以称之为下界
List<? super Button> buttons = new ArrayList<TextView>();
Object object = buttons.get(0); // get 出来的是 Object 类型
Button button = ...
buttons.add(button); // add 操作是可以的
Button 对象一定是这个未知类型的子类型,根据多态的特性,这里通过 add 添加 Button 对象是合法的,使用下界通配符 ? super 的泛型 List,只能读取到 Object 对象,通常也只拿它来添加数据,这种泛型类型声明称之为「消费者 Consumer」
小结:
Java 的泛型本身是不支持协变和逆变的。
可以使用泛型通配符 ? extends 来使泛型支持协变,但是「只能读取不能修改」,这里的修改仅指对泛型集合添加元素,如果是 remove(int index) 以及 clear 当然是可以的。
可以使用泛型通配符 ? super 来使泛型支持逆变,但是「只能修改不能读取」,这里说的不能读取是指不能按照泛型类型读取,你如果按照 Object 读出来再强转当然也是可以的。
根据前面的说法,这被称为 PECS 法则:「Producer-Extends, Consumer-Super」
参考码上开学Kotlin 的泛型
在JDK 1.7里的java.util.Collection ,copy方法,可以说明out 和in 使用的情况
Kotlin:
使用关键字 out 来支持协变,等同于 Java 中的上界通配符 ? extends。
使用关键字 in 来支持逆变,等同于 Java 中的下界通配符 ? super。
符号
java中,单个"?“相当于? extends Object。
Kotlin中等效写法,”*",相当于out Any。当类型实参的信息并不重要的时候,可以使用星号投影的语法:不需要使用任何在签名中引用类型参数的方法,或者只是读取数据而不关心它的具体类型。
fun printFirst(list: List<*>) {
if (list.isNotEmpty()) {
println(list.first())
}
}
where
在java中,如果extends多个边界,需要&符号连接
class Monster<T extends Animal & Food>{
}
在Kotlin中,可以使用where来连接
class Monster<T> where T : Animal, T : Food
java和kotlin泛型的区别:
Java 里的数组是支持协变的,而 Kotlin 中的数组 Array 不支持协变
Java 中的 List 接口不支持协变,而 Kotlin 中的 List 接口支持协变