Android进阶宝典 -- Java/Kotlin中的泛型

144 阅读5分钟

无论是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)

报错了,提示类转换异常;因为没有在编译期提醒导致了运行时出现类转换异常,所以才出现了泛型,有了泛型,就能在编译期发现错误。

image.png

而在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是一样的,为什么?难道泛型在运行时失效了吗?

image.png

通过查看字节码文件,我们发现两个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赋值了,但是

image.png

不能往集合中添加元素,是因为泛型擦除之后,这个集合具体往里放什么元素是不知道的,是苹果?还是香蕉?都不确定,取出的元素也是不确定的,但一定是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,目的就是灵活地转型