泛型相关笔记

泛型的目的

在编译阶段完成类型的转换的工作,避免在运行时强制类型转换而出现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连问

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • Java编程思想---泛型(2) 类型擦除 先上例子: ArrayList< String >与ArrayList...
    Cool_Pomelo阅读 234评论 0 0
  • JAVA-泛型 sschrodinger 2018/11/15 简介 泛型是Java SE 1.5的新特性,泛型的...
    sschrodinger阅读 508评论 0 2
  • [TOC] 深入理解 Java 泛型 概述 泛型的本质是参数化类型,通常用于输入参数、存储类型不确定的场景。相比于...
    albon阅读 5,268评论 0 7
  • 最近终于开始总结自己理解的东西,终于有时间写写自己对一些知识的认识。文笔不好,还望各位包含。以下知识点是我看了享学...
    Jack_Ou阅读 727评论 0 2
  • http://www.importnew.com/24029.html 泛型基础 泛型类 这样做的坏处是 Box ...
    ColdWave阅读 346评论 0 0