Kotlin与Java泛型
- 使用参数化类型的概念,允许在不指定具体类型的情况下进行编程,将类型当作参数传递给一个类或者是方法
1.基本使用
泛型类
- 在类名后面使用 ,此语法结构是给类定义一个泛型
class MyClass<T> {
fun method(params: T) {}
}
泛型方法
- 在方法名的前面加上 , 此语法结构给方法定义一个泛型
fun <T> method(params: T) {}
泛型接口
- 在接口名后面加上 此语法结构给接口定义一个泛型
interface MyInterface<T> {
fun interfaceMethod(params: T)
}
2.泛型边界
- 在泛型的参数上设置限制条件,这样可以强制泛型可以使用的类型,更重要的是可以按照自己的边界类型来调用方法
单个边界
- 使用
<T : Class>
这种语法结构,如果不指定泛型的边界,默认为 Any?
class MyClass<T : Number> {
fun <T : Number> method(params: T) {}
}
多个边界
- 如果有多个边界,可以使用 where 关键字,
- 中间使用 : 隔开,多个边界中只能有一个边界是类,且类必须放在最前面
open class Animal
interface Eat
interface Sleep
class MyAnimal<T> where T : Animal, T : Eat, T : Sleep {
fun <T> method(params: T) where T : Animal, T : Eat, T : Sleep {}
}
3.泛型擦除
-
通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上
-
泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉
之所以要使用泛型擦除是为了兼容 JDK 1.5 之前运行时的类加载器,避免因为引入泛型而导致运行时创建不必要的类
JAVA泛型擦除的具体步骤
-
擦除泛型类型参数信息,遵循如下规则进行擦除
<T> 擦除后变为 Object <T extends A> 擦除后变为 A <? extends A> 擦除后变为 A <? super A> 擦除后变为Object
如果类型参数是有界的,则将每个参数替换为其第一个边界
如果类型参数是无界的,则将其替换为 Object类型擦除的规则,此行为会使很多方法失效
-
如果需要则插入类型转换,以保持类型安全。
public class JavaGenericWipe { public static void main(String[] args) { List<String> stringList = new ArrayList<>(); stringList.add("erdai"); stringList.add("666"); for (String s : stringList) { System.out.println(s); } } } //编译时生成的字节码文件翻译过来大致如下 public class JavaGenericWipe { public JavaGenericWipe() { } public static void main(String[] args) { List<String> stringList = new ArrayList(); stringList.add("erdai"); stringList.add("666"); Iterator var2 = stringList.iterator(); while(var2.hasNext()) { //编译器做了强转的工作 String s = (String)var2.next(); System.out.println(s); } } }
会在某些情况下生成的字节码文件之中,对其进行类型转换,保证类型安全
-
如果需要则生成桥接方法以在子类中保留多态性
class Node { public Object data; public Node(Object data) {this.data = data;} public void setData(Object data) { this.data = data;} } class MyNode extends Node { public MyNode(Integer data) {super(data);} public void setData(Integer data) {super.setData(data);} } //编译时生成的字节码文件翻译过来大致如下 class MyNode extends Node { public MyNode(Integer data) {super(data);} // 编译器生成的桥接方法 public void setData(Object data) {setData((Integer) data);} public void setData(Integer data) { System.out.println("MyNode.setData"); super.setData(data); } }
会在某些情况下生成的字节码文件中,生成桥接方法,实现多态
4.泛型实化
- 泛型实化是Kotlin独有的,其实现原理是通过inline函数的特性,会对代码进行替换,即在inline函数内部的泛型,最终也会通过实际的类型进行替换
//通过inline实现startActivity的泛型实化
inline fun <reified T> Context.startActivity(block: Intent.() -> Unit) {
val intent = Intent(this, T::class.java)
intent.block()
this.startActivity(intent)
}
5.泛型协变,逆变,不变
协变
-
语法规则为
<out T>
类似于 Java 的<? extends Bound>
-
它限定的类型是当前上边界类或者其子类,如果是接口的话就是当前上边界接口或者实现类
-
协变的泛型变量只读,不可以写,可以添加 null ,但是没意义
限定的类型是当前上边界类或者其子类,它无法确定自己具体的类型,因此编译器无法验证类型的安全,所以不能写
class Simple<out T> {}
逆变
-
语法规则为
<in T>
类似于 Java 的<? super Bound>
-
它限定的类型是当前下边界类或者其父类,如果是接口的话就是当前下边界接口或者其父接口
-
逆变的泛型变量只能写,不建议读
1.限定的类型是当前下边界类或者其父类,虽然它也无法确定自己具体的类型,但根据多态,它能保证自己添加的元素是安全的,因此可以写
2.获取值的时候,会返回一个
Object
类型的值,而不能获取实际类型参数代表的类型,因此建议不要去读,如果你实在要去读也行,但是要注意类型转换异常
class Simple<in T> {}
不变
- 语法规则为,和Java一致
- 指泛型实际类型没有任何继承关系,是不变的
class SimpleData<T>{}
无界
- 语法规则为 <*>,它等价于
<out Any>
,类似于 Java 中的 <?>,<?>
实际上它等价于<? extends Object>
,也就是说它的上边界是 Object 或其子类,因此使用无界通配符的变量同样只读,不能写,可以添加 null ,但是没意义
- 在定义一个类的时候你如果使用
<out T : Number>
,那么 * 就相当于<out Number>
val a: ArrayList<*> = ArrayList<Int>()
转载自:https://juejin.cn/post/7007021317371199524