无论是Java还是Kotlin,泛型随处可见,而且在框架架构中,泛型起到了非常重要的作用;那么泛型的出现,是为了解决什么事呢,看下下面这个场景:
open class Fruit {
open fun description():String{
return ""
}
}
class Apple : Fruit() {
override fun description():String{
return "This is Apple"
}
}
class Banana : Fruit() {
override fun description(): String {
return "This is Banana"
}
}
public static void test() {
ArrayList list = new ArrayList();
list.add(new Apple());
list.add(new Banana());
Banana banana = (Banana) list.get(0);
}
如果在Java中,可以创建一个ArrayList集合,这个集合中可以放任何对象,当我们执行这段代码时:
Caused by: java.lang.ClassCastException: com.lay.language.generic.Apple cannot be cast to com.lay.language.generic.Banana
at com.lay.language.generic.GenericTest.test(GenericTest.java:15)
报错了,提示类转换异常;因为没有在编译期提醒导致了运行时出现类转换异常,所以才出现了泛型,有了泛型,就能在编译期发现错误。
而在Kotlin中,因为存在很多语法糖,在声明集合的时候就需要强制你设置一个泛型对象
fun test(){
val list = mutableListOf<Banana>()
list.add(Apple()) //这个地方会提示错误
list.add(Banana())
}
看下源码
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun <T> mutableListOf(): MutableList<T> = ArrayList()
1 泛型类型
1.1 Java中的泛型类型
在Java中存在3种泛型类型:泛型接口、泛型类、泛型方法
public interface IFactory<T> {
void add(T t);
T get();
}
public class AppleFactory<T> implements IFactory<T>{
private List<T> list = new ArrayList<T>();
@Override
public void add(T t) {
list.add(t);
}
@Override
public T get() {
return list.get(0);
}
}
@Override
public <E> E get(E e) {
return e;
}
其中泛型接口和泛型类比较简单,但是泛型方法,我们看它其实是有自己的模式,看注释
@Override
// <E> 声明了这个方法是一个泛型方法,声明了泛型的类型E,这个类型可以不是泛型类中的类型 T
// E 代表该方法的返回值类型
// (E e)定义了泛型参数
public <E> E get(E e) {
return e;
}
所以对于泛型方法来说,更加灵活。
1.2 Kotlin中的泛型类型
跟Java一致,Kotlin中的泛型类型也包括泛型接口、泛型类、泛型方法
interface IMethod<T> {
fun getMethod(): T
fun addMethod(t: T)
}
class MyMethod<T>:IMethod<T> {
val list = mutableListOf<T>()
override fun getMethod(): List<T> {
return list
}
override fun addMethod(t: T) {
list.add(t)
}
}
open fun <E> getMethods(e:E):MutableList<E>{
return mutableListOf()
}
1.3 泛型在静态类和静态方法中的影响
public static T t;
public static T getT(){
}
在使用泛型的时候,这种使用方式是✅还是❌,显然是错误的,因为泛型定义就是因为类型是未知的,如果是一个静态变量就需要一开始就完成初始化,显然不能满足诉求。
public static <T> T getT(){
return null;
}
但是泛型方法是没有问题的,是因为这个泛型T跟类中的泛型T不是一个,只有在调用的时候,才会确定类型,所以没有影响
1.4 泛型数组
有泛型数组吗?没有
我们在日常开发工作中,好像并没有使用过泛型数组,是因为泛型擦除的原因导致数组不能满足协变。什么是协变呢?
//假设 A extends B
//那么 A<T>[]数组就是B<T>[]数组的协变
当泛型擦除之后,就不存在协变这个关系了。
2 泛型的本质
无论是Kotlin还是Java,都在编译期避免类型错误,但是在运行时是什么样的呢?
public static void test() {
ArrayList<Banana> list = new ArrayList();
ArrayList<Apple> list2 = new ArrayList();
Log.e("TAG","结果:${}"+(list.getClass() == list2.getClass()));
}
我们可以看到,List和list2是两个不同的对象,两个对象泛型类型也不一样,那么在运行时,我们发现两个对象的class是一样的,为什么?难道泛型在运行时失效了吗?
通过查看字节码文件,我们发现两个ArrayList中并没有泛型的信息,而且两个class为什么一样在这里就能看到,原来泛型在编译的时候被擦除了,被替换成了Object。
2.1 桥方法
仅仅是将泛型擦除,替换成了Object这么简单吗?其实不是的,我们先一步一步来看
public interface IFactory<T> {
void add(T t);
T get();
}
我们可以看到,泛型接口编译后,泛型T被擦除,方法中的泛型参数被替换成Object
public abstract interface com/lay/language/generic/method/IFactory {
// compiled from: IFactory.java
// access flags 0x401
// signature (TT;)V
// declaration: void add(T)
public abstract add(Ljava/lang/Object;)V
// access flags 0x401
// signature ()TT;
// declaration: T get()
public abstract get()Ljava/lang/Object;
}
接下来看下泛型类,只是实现了IFactory接口
public class AppleFactory<T> implements IFactory<T> {
private List<T> list = new ArrayList<T>();
@Override
public void add(T t) {
list.add(t);
}
@Override
public T get() {
return list.get(0);
}
}
我们可以看到,AppleFactory和IFactory的泛型已经被擦除了,add方法和get方法中的泛型参数也被替换成了Object,这里还是正常的。
public class com/lay/language/generic/method/AppleFactory implements com/lay/language/generic/method/IFactory {
// compiled from: AppleFactory.java
// access flags 0x2
// signature Ljava/util/List<TT;>;
// declaration: list extends java.util.List<T>
private Ljava/util/List; list
// access flags 0x1
public <init>()V
L0
LINENUMBER 11 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 13 L1
ALOAD 0
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
PUTFIELD com/lay/language/generic/method/AppleFactory.list : Ljava/util/List;
RETURN
L2
LOCALVARIABLE this Lcom/lay/language/generic/method/AppleFactory; L0 L2 0
// signature Lcom/lay/language/generic/method/AppleFactory<TT;>;
// declaration: this extends com.lay.language.generic.method.AppleFactory<T>
MAXSTACK = 3
MAXLOCALS = 1
// access flags 0x1
// signature (TT;)V
// declaration: void add(T)
public add(Ljava/lang/Object;)V
L0
LINENUMBER 17 L0
ALOAD 0
GETFIELD com/lay/language/generic/method/AppleFactory.list : Ljava/util/List;
ALOAD 1
INVOKEINTERFACE java/util/List.add (Ljava/lang/Object;)Z (itf)
POP
L1
LINENUMBER 18 L1
RETURN
L2
LOCALVARIABLE this Lcom/lay/language/generic/method/AppleFactory; L0 L2 0
// signature Lcom/lay/language/generic/method/AppleFactory<TT;>;
// declaration: this extends com.lay.language.generic.method.AppleFactory<T>
LOCALVARIABLE t Ljava/lang/Object; L0 L2 1
// signature TT;
// declaration: t extends T
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
// signature ()TT;
// declaration: T get()
public get()Ljava/lang/Object;
L0
LINENUMBER 22 L0
ALOAD 0
GETFIELD com/lay/language/generic/method/AppleFactory.list : Ljava/util/List;
ICONST_0
INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object; (itf)
ARETURN
L1
LOCALVARIABLE this Lcom/lay/language/generic/method/AppleFactory; L0 L1 0
// signature Lcom/lay/language/generic/method/AppleFactory<TT;>;
// declaration: this extends com.lay.language.generic.method.AppleFactory<T>
MAXSTACK = 2
MAXLOCALS = 1
}
如果泛型类的泛型是带限制的,例如AppleFactory中的泛型继承自Comparable接口
public class AppleFactory<T extends Comparable<T>> implements IFactory<T> {
private List<T> list = new ArrayList<T>();
@Override
public void add(T t) {
list.add(t);
}
@Override
public T get() {
return list.get(0);
}
}
看下编译后的字节码,有两个变化:
(1)add和get方法中的参数,由Object换成了Comparable;
(2)生成两个桥方法 bridge get和bridge add,他们的泛型参数还是Object
public class com/lay/language/generic/method/AppleFactory implements com/lay/language/generic/method/IFactory {
// compiled from: AppleFactory.java
// access flags 0x2
// signature Ljava/util/List<TT;>;
// declaration: list extends java.util.List<T>
private Ljava/util/List; list
// access flags 0x1
public <init>()V
L0
LINENUMBER 11 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 13 L1
ALOAD 0
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
PUTFIELD com/lay/language/generic/method/AppleFactory.list : Ljava/util/List;
RETURN
L2
LOCALVARIABLE this Lcom/lay/language/generic/method/AppleFactory; L0 L2 0
// signature Lcom/lay/language/generic/method/AppleFactory<TT;>;
// declaration: this extends com.lay.language.generic.method.AppleFactory<T>
MAXSTACK = 3
MAXLOCALS = 1
// access flags 0x1
// signature (TT;)V
// declaration: void add(T)
public add(Ljava/lang/Comparable;)V
L0
LINENUMBER 17 L0
ALOAD 0
GETFIELD com/lay/language/generic/method/AppleFactory.list : Ljava/util/List;
ALOAD 1
INVOKEINTERFACE java/util/List.add (Ljava/lang/Object;)Z (itf)
POP
L1
LINENUMBER 18 L1
RETURN
L2
LOCALVARIABLE this Lcom/lay/language/generic/method/AppleFactory; L0 L2 0
// signature Lcom/lay/language/generic/method/AppleFactory<TT;>;
// declaration: this extends com.lay.language.generic.method.AppleFactory<T>
LOCALVARIABLE t Ljava/lang/Comparable; L0 L2 1
// signature TT;
// declaration: t extends T
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
// signature ()TT;
// declaration: T get()
public get()Ljava/lang/Comparable;
L0
LINENUMBER 22 L0
ALOAD 0
GETFIELD com/lay/language/generic/method/AppleFactory.list : Ljava/util/List;
ICONST_0
INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object; (itf)
CHECKCAST java/lang/Comparable
ARETURN
L1
LOCALVARIABLE this Lcom/lay/language/generic/method/AppleFactory; L0 L1 0
// signature Lcom/lay/language/generic/method/AppleFactory<TT;>;
// declaration: this extends com.lay.language.generic.method.AppleFactory<T>
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x1041
public synthetic bridge get()Ljava/lang/Object;
L0
LINENUMBER 11 L0
ALOAD 0
INVOKEVIRTUAL com/lay/language/generic/method/AppleFactory.get ()Ljava/lang/Comparable;
ARETURN
L1
LOCALVARIABLE this Lcom/lay/language/generic/method/AppleFactory; L0 L1 0
// signature Lcom/lay/language/generic/method/AppleFactory<TT;>;
// declaration: this extends com.lay.language.generic.method.AppleFactory<T>
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1041
public synthetic bridge add(Ljava/lang/Object;)V
L0
LINENUMBER 11 L0
ALOAD 0
ALOAD 1
CHECKCAST java/lang/Comparable
INVOKEVIRTUAL com/lay/language/generic/method/AppleFactory.add (Ljava/lang/Comparable;)V
RETURN
L1
LOCALVARIABLE this Lcom/lay/language/generic/method/AppleFactory; L0 L1 0
// signature Lcom/lay/language/generic/method/AppleFactory<TT;>;
// declaration: this extends com.lay.language.generic.method.AppleFactory<T>
MAXSTACK = 2
MAXLOCALS = 2
}
为什么会生成桥方法呢?是因为我们在泛型类中定义了一个严格的泛型类型,需要继承自Comparable接口,那么对应的get和add方法泛型擦除之后,被替换成了Comparable;
但是我们实现了IFactory接口,正常情况下,父类中的方法泛型参数擦除之后应该是Object,但是现在变成了Comparable,这样的话就相当于没有重写父类的这两个方法,所以编译期生成了两个桥方法,来重写父类的方法
其实这两个桥方法就是如下的作用:
public synthetic bridge get()Ljava/lang/Object{
return (Object)get()
}
public synthetic bridge add(Ljava/lang/Object;){
add((Comparable)object)
}
2.2 反射获取泛型类型
前面我们提到,在编译期泛型被擦除了,那么反射的时候,我们能拿到泛型信息吗?先试一下
public class AFactory extends AppleFactory<String>{
List<Apple> list = new ArrayList();
}
例如在AFactory中有一个变量list,通过反射获取这个属性
val listFiled = AFactory::class.java.getDeclaredField("list")
Log.e("TAG","type --${listFiled.genericType}")
在反射中提供了一个方法,genericType能够获取这个属性的泛型类型
2022-10-07 17:33:41.927 8358-8358/com.lay.mvi E/TAG: type --java.util.List<com.lay.language.generic.Apple>
泛型被擦除,还能获取泛型信息,泛型信息是存储在哪里?类的常量池中
3 通配符
3.1 型变
List<Apple> list = new ArrayList();
List<Fruit> fruitList = new ArrayList<>();
List<Fruit> getFruitList = (List<Fruit>) list;
我们看一个示例,Apple是继承自Fruit,虽然我们可以在fruitList中添加Apple,但是无法将list强转为Fruit类型的List,Java中是不允许的,是因为Java中的泛型不是型变的,容器之间是没有继承关系的
val list = mutableListOf<Apple>()
list.add(Apple())
val list2:MutableList<Fruit> = list as MutableList<Fruit>
Log.e("TAG","list $list2")
但是在Kotlin中,泛型是型变的,也就意味着Apple和Fruit是继承关系,对应的List集合能够互相强转
3.2 协变
我们知道在Java中的泛型不是型变的,那么有什么办法能够类型转换吗?我们可以使用通配符
List<Apple> appleList = new ArrayList<>();
//定义通配符
List<? extends Fruit> list = appleList; ✅
extends通配符,定义了这个集合的上限,意味着所有Fruit子类的集合都可以给list赋值了,但是
不能往集合中添加元素,是因为泛型擦除之后,这个集合具体往里放什么元素是不知道的,是苹果?还是香蕉?都不确定,取出的元素也是不确定的,但一定是Fruit。
在Kotlin中,与extends对应的是out,跟Java一样,都是只能取不能存数据
val list:MutableList<out Fruit> = mutableListOf()
3.3 逆变
既然有能取,就有能存的,肯定是成对出现的,super关键字就能解决这个问题
List<? super Fruit> list = new ArrayList<Food>();
list.add(new Food());
super定义了集合的下界,意味着所有Fruit的父类都可以添加进来,但是不能取值,因为取值的时候泛型信息丢失了,无法判断其类型。
Kotlin中与之对应的关键字为in
val list:MutableList<in Fruit> = mutableListOf()
list.add(Food())
3.3 泛型的PECS原则
如果从集合中获取T类型元素,可以使用<? extends T>通配符;
如果需要往集合中放入T类型元素,可以使用<? super T>通配符;
如果既需要存又需要取,可以使用List<T>
总之遵循的原则PECS ----> Producer extends Cosumer super,目的就是灵活地转型