泛型JDK8

泛型相关笔记

2021-06-30  本文已影响0人  风月寒
泛型的目的

在编译阶段完成类型的转换的工作,避免在运行时强制类型转换而出现ClassCastException,类型转化异常。

泛型的使用
泛型类

将泛型定义在类上

public class 类名 <泛型类型1,...> {
    
}

需要注意的点:

1、泛型在创建对象时,没有指定具体的数据类型时,按照object

2、泛型不支持基本数据类型

3、同一泛型根据 不同的数据类型创建的对象本质上是同一类型

ArrayList<String> list1 = new ArrayList()
ArrayList<Integer> list2 = new ArrayList()
list1.getClass == list2.getClass  //true

造成这样的原因是泛型擦除。

4、子类是泛型,子类要和父类的泛型类一致

5、子类不是泛型,父类则需要传入明确的泛型类型

错误的方式:
public class A extends Container<K, V> {}

正确的方式:
public class A extends Container<Integer, String> {}

也可以不指定具体的类型,系统就会把K,V形参当成Object类型处理
public class A extends Container {}

泛型方法

把泛型定义在方法上

public <泛型类型> 返回类型 方法名(泛型类型 变量名) {
    
}
泛型接口

把泛型定义在接口

public interface 接口名<泛型类型> {
    
}

需要注意的是:

1、实现类不是泛型类,接口需要明确数据类型

2、实现类是泛型类,接口和实现类的泛型是一致

通配符

理解通配符,需要知道一句话,==容器中装的东西有继承关系,但容器之间是没有继承关系的。==

上界通配符:<? extends T>

例如Plate<? extends Fruits> Fruits是所有的上界,只要是Fruits的子类都满足要求。即继承Fruits或者实现Fruits的子类都满足要求。

有一个缺点,只能往外面取东西,不能往里面存东西,即get有效,set会报错。原因是编译器只知道容器内是Fruit或者它的派生类,所以取时不能用具体的派生类接收。只能用Fruit或者Fruit的基类接收。

上界通配符Plate<? extends Fruit>里面放什么不确定,都是Fruit的派生类。存
操作时,接收却只是用了一个占位符或者通配符来接收,来表示捕获一个Fruit类或其子类,具体什么类不知道。然后无论是想往里面插入Apple或者Meat或者Fruit编译器都不知道能不能和这个占位符匹配,所以存操作都不允许。

下界通配符<? super T>

例如Plate<? super Fruits> 其下界就是Fruits,所以Fruits是最低的,只能是Fruits的父类才能满足条件

有一个缺点,下界<? super T>不影响往里面存,但是往外取时只能放到Object对象里面去,不能放到具体的对象中去

因为下界规定了元素的最小粒度的下限,实际上是放松可容器元素的类型控制。因为存放的元素都是Fruit的基类,那只
要往里面存的粒度都比Fruit小或等都可以。但往外读取元素就费劲了,只有所有类的基类Objeect对象才能保证每次都装下。
但是这样的话,元素的类型信息就全部丢失了。

PECS原则

PECS的意思是Producer Extend Consumer Super,简单理解为如果是生产者则使用Extend,如果是消费者则使用Super

PECS是从集合的角度出发的

1.如果你只是从集合中取数据,那么它是个生产者,你应该用extend

2.如果你只是往集合中加数据,那么它是个消费者,你应该用super

3.如果你往集合中既存又取,那么你不应该用extend或者super

优点:

使用PECS主要是为了实现集合的多态

泛型擦除

Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程就是泛型擦除。

既然有泛型擦除,那像retrofit怎么获取类型?

要了解这个,需要先了解Type与泛型的关系。

Type
public interface Type {
    
    default String getTypeName() {
        return toString();
    }
}

Type 接口有一个我们熟悉的实现类 Class 类, 还有四个接口类型TypeVariable、ParameterizedType、WildcardType、GenericArrayType。

TypeVariable

泛型参数 T,TypeVariable 完全就是变量如:T 而不是一种确切的类型。

public interface TypeVariable<D extends GenericDeclaration> extends Type/*, AnnotatedElement*/ {
   //返回泛型参数的上界列表, 泛型的声明只有 extends 形式
    Type[] getBounds();

     //泛型参数是在哪里形式声明的,Method 、Constructor、Class
    D getGenericDeclaration();

    
    String getName();
}
ParameterizedType

参数化类型

public interface ParameterizedType extends Type {
    // 获取 < > 内的 参数信息 。如HashMap< String, T>  中的 String 和 T 。
    // String 是 class 类型 ,T 是TypeVariable 类型
    Type[] getActualTypeArguments();
    // 获取原生类型 ,如 List<T> 的 List 类型它属于 Class 类型。
    Type getRawType();
    // 获取外部类的类型,如果没有外部类,返回Null
    Type getOwnerType();
}
WildcardType

带通配符的类型 。也就是 HashMap<? extends CharSequence, T> 的第一个泛型参数 ? extends CharSequence 。
它的作用和 TypeVariable 一样,是用来描述参数信息的, 和原生类型无关。

public interface WildcardType extends Type {
   // 获取  extends  的类型
    Type[] getUpperBounds();
    //获取 super 的类型 
    Type[] getLowerBounds();
}
GenericArrayType
public interface GenericArrayType extends Type {
    // 获取数组的类型
    Type getGenericComponentType();
}

而我们的retrofit就是根据调用method的getGenericReturnType()来获取

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {
    
    Type adapterType;
    if (isKotlinSuspendFunction) {
      Type[] parameterTypes = method.getGenericParameterTypes();
      Type responseType = Utils.getParameterLowerBound(0,
          (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
      if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
        // Unwrap the actual body type from Response<T>.
        responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
        continuationWantsResponse = true;
      } else {
        // TODO figure out if type is nullable or not
        // Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
        // Find the entry for method
        // Determine if return type is nullable or not
      }

      adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
      annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
    } else {
      adapterType = method.getGenericReturnType();//1
    }

getGenericReturnType 获取带泛型信息的返回类型 、

getGenericParameterTypes 获取带泛型信息的参数类型。

虽然在编译时,泛型的信息会被擦除,但是某些(声明侧的泛型,接下来解释) 泛型信息会被class文件 以Signature的形式 保留在Class文件的Constant pool中。

通过javap命令 可以看到在Constant pool中#5 Signature记录了泛型的类型。

Constant pool:
   #1 = Class              #16            //  com/example/diva/leet/GitHubService
   #2 = Class              #17            //  java/lang/Object
   #3 = Utf8               listRepos
   #4 = Utf8               (Ljava/lang/String;)Lretrofit2/Call;
   #5 = Utf8               Signature
   #6 = Utf8               (Ljava/lang/String;)Lretrofit2/Call<Ljava/util/List<Lcom/example/diva/leet/Repo;>;>;
   #7 = Utf8               RuntimeVisibleAnnotations
   #8 = Utf8               Lretrofit2/http/GET;
   #9 = Utf8               value
  #10 = Utf8               users/{user}/repos
  #11 = Utf8               RuntimeVisibleParameterAnnotations
  #12 = Utf8               Lretrofit2/http/Path;
  #13 = Utf8               user
  #14 = Utf8               SourceFile
  #15 = Utf8               GitHubService.java
  #16 = Utf8               com/example/diva/leet/GitHubService
  #17 = Utf8               java/lang/Object
{
  public abstract retrofit2.Call<java.util.List<com.example.diva.leet.Repo>> listRepos(java.lang.String);
    flags: ACC_PUBLIC, ACC_ABSTRACT
    Signature: #6                           // (Ljava/lang/String;)Lretrofit2/Call<Ljava/util/List<Lcom/example/diva/leet/Repo;>;>;
    RuntimeVisibleAnnotations:
      0: #8(#9=s#10)
    RuntimeVisibleParameterAnnotations:
      parameter 0:
        0: #12(#9=s#13)
}

声明侧泛型会被记录在 Class 文件的 Constant pool 中 :

泛型类,或泛型接口的声明
带有泛型参数的方法
带有泛型参数的成员变量

而使用侧泛型则不会被记录 :也就是方法的局部变量, 方法调用时传入的变量。

比如像Gson解析时传入的参数属于使用侧泛型,因此不能通过Signature解析

Gson是如何获取到List<String>的泛型信息String的呢?

Class类提供了一个方法public Type getGenericSuperclass() ,可以获取到带泛型信息的父类Type。

也就是说java的class文件会保存继承的父类或者接口的泛型信息。

所以Gson使用了一个巧妙的方法来获取泛型类型:

1.创建一个泛型抽象类TypeToken <T> ,这个抽象类不存在抽象方法,因为匿名内部类必须继承自抽象类或者接口。所以才定义为抽象类。

2.创建一个 继承自TypeToken的匿名内部类, 并实例化泛型参数TypeToken<String>

3.通过class类的public Type getGenericSuperclass()方法,获取带泛型信息的父类Type,也就是TypeToken<String>

总结:Gson利用子类会保存父类class的泛型参数信息的特点。 通过匿名内部类实现了泛型参数的传递。

参考文献:

Java Type 类型详解

java 的泛型擦除与 TypeToken

【知识点】Java泛型机制7连问

上一篇下一篇

猜你喜欢

热点阅读