java基础:泛型(2022)

入职新公司也快转正了,是时候再看看总结之前的东西,毕竟最近各大公司裁员,寒冬真正降临了。希望下次到自己时候不那么焦虑,现在能做的就是提前准备好,随时保持自己可以直接去参加并通过面试。

泛型

直接问几个问题吧,看下是否能回答上。

1.泛型出现的背景、版本、目的?

2.泛型类怎么定义,泛型接口如何定义以及怎么使用?

3.泛型方法的定义格式,跟泛型类普通类是什么关系?

4.泛型能不能用基本数据类型,当a继承b,泛型a,和泛型b有没有关系?

5.泛型的继承怎么使用?

6.什么是泛型擦出?为什么要擦出?擦出了怎么知道是什么类型?知道Gson和Retrofit分别是怎么处理的吗?是否知道JRE和IDE对泛型的检查区别?

问题回答:

1.泛型出现的背景、版本、目的?

泛型是在JDK1.5版本引入,主要是在引入泛型之前,我们可能有一些类里面有很多的方法重载,比如一个计算工具类 ,里面只是输入的参数类型不同,我们需要写3个方法,其中很多重复代码:

         public void  add(int data,int data2){
            data =  data+data2;
        }
        public void  add(float data,float data2){
            data = data+data2;
        }
        public void  add(double data,double data2){
            data = data+data2;
        }

如果我们使用泛型:

class Utils<T> {
            public void  add(T data ,T data2){
            }
        }

另外一个场景:
我们往一个List里面是可以添加任意类型的数据,本来是要添加int类型的来做计算,但没有泛型之前添加了一个string 类型的数据,就可能造成一些计算或者强制类型转换出错,这个出错发生在运行期间。所以泛型的出现目的是把运行时的问题提前到编译时发现。

结论:泛型是JDK1.5版本出现的,主要出现目的是在上面描述的一些场景把运行时的问题提前在编译期暴露出来,另外能让我们写更少的重复代码提高代码的封装。
2.泛型类怎么定义,泛型接口如何定义以及怎么使用?

泛型类也就是在类增加 < T >方式,T可以是任意的字母表示,泛型接口同样方式,一般实现一个泛型接口,要么也不指定具体的类型,也用T暂时替代,要么就明确指定类型如下:

        interface  IRequestListener <K>{
            K success();
        }

        class ImplClass<T> implements IRequestListener<T> {

            @Override
            public T success() {
                return null;
            }
        }
        
        public void getData(){
            new IRequestListener<String>(){
                @Override
                public String success() {
                    return null;
                }
            };
        }
3.泛型方法的定义格式,跟泛型类普通类是什么关系?

泛型方法的定义是对比普通方法 在修饰类型和返回值类型之间多了一个 < T >
如下:

class ImplClass<T> implements IRequestListener<T> {

            @Override
            public T success() {
                return null;
            }
          //public String getString(String s){} 
            public <K> K getData(K data){
                return data;
            } 
        }
4.泛型能不能用基本数据类型,当a继承b,泛型a,和泛型b有没有关系?

没有任何关系

 public class PP<T>{
       }
       class A{}
       class  B extends A{}
        A a = new B();
        PP<A> p = new PP<B>(); //这个是会报错的,也就是泛型包装了的类不再是继承关系
5.泛型的继承怎么使用?

有两种使用方式:
? extends X 表示类型的上界,类型参数是X的子类
? super X 表示类型的下界,类型参数是X的超类
这个理解比较绕吧,个人理解,如果定义了一个Friut类, 使用 ? extends Friut类,里面提供get和set方法,哪个能用呢?很多高级工程师也说不清具体的:

//        testType< Fruit> tt = new testType<Apple>(); //直接这么写会报错,需要下面的写法
        testType<? extends Fruit> tt = new testType<Apple>(); //但是这个写法后,只能拿,因为只知道这个是Fruit类型的,获取的肯定是Fruit,但是去设置,不确定是苹果还是别的水果
        Fruit data = tt.getData();
        tt.setData(new Apple()); //这个会报错


//        testType<Apple> ttt = new testType<Fruit>(); //同样会报错
        testType< ? super Apple > ttt = new testType<Fruit>(); //同样拿实际的右边一定是苹果的父亲,但是是父亲还是祖先不确定,
        ttt.setData(new Apple());
        Object data1 = ttt.getData(); //这个获取的是

简单理解 ?extends T 这种是类型一定是T的子类,可以获取到T 但是不能设置数据,因为T有很多子孙;? extends X 表示类型的上界,类型参数是X的子类,那么可以肯定的说,get方法返回的一定是个X(不管是X或者X的子类)编译器是可以确定知道的。但是set方法只知道传入的是个X,至于具体是X的那个子类,不知道。
总结:主要用于安全地访问数据,可以访问X及其子类型,并且不能写入非null的数据。

?super T这种是T的超类,可以拿到具体的类型,但不能用set去限制。? super X 表示类型的下界,类型参数是X的超类(包括X本身),那么可以肯定的说,get方法返回的一定是个X的超类,那么到底是哪个超类?不知道,但是可以肯定的说,Object一定是它的超类,所以get方法返回Object。编译器是可以确定知道的。对于set方法来说,编译器不知道它需要的确切类型,但是X和X的子类可以安全的转型为X。
总结:主要用于安全地写入数据,可以写入X及其子类型。

6.什么是泛型擦出?为什么要擦出?擦出了怎么知道是什么类型?知道Gson和Retrofit分别是怎么处理的吗?是否知道JRE和IDE对泛型的检查区别?

我们在开发时如果写以下代码就会报错:

    public  String getData(ArrayList<String> list){
        return "";
    }
    public  int getData(ArrayList<Integer> list){
        return 123;
    }

因为存在泛型擦出问题,因为Java的范型只存在于源码里,编译的时候给你静态地检查一下范型类型是否正确,而到了运行时就不检查了。代码在JRE(Java运行环境)其实就是没有泛型类型的

泛型擦出的目的:
为了向下兼容。
Java 5才引入了范型,在此之前都是没有范型的。当时引入范型只是为了解决遍历集合的时候总要手动强制转型的问题,
比如我们打印下面的代码, 是一样的,如果为了证明,还可以反射往一个指定泛型为int类型的数据添加string能成功也说明了泛型擦出:

List<Integer> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass());

retrofit/ Gson是怎么获得擦除后的类型的?
比如retrofit代码:

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

擦出后是这样子:

public interface GitHubService
{
     public abstract Call listRepos(String s);
}

那么Retrofit是如何拿到Call<List>的类型信息?

static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    ...
    Type returnType = method.getGenericReturnType();
    ...
  }
 
    public Type getGenericReturnType() {
       // 根据 Signature 信息 获取 泛型类型 
      if (getGenericSignature() != null) {
        return getGenericInfo().getReturnType();
      } else { 
        return getReturnType();
      }
    }

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

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

// Gson 常用的情况

    public  List<String> parse(String jsonStr){
        List<String> topNews =  new Gson().fromJson(jsonStr, new TypeToken<List<String>>() {}.getType());
        return topNews;

声明侧泛型会被记录在Class文件的Constant pool中,使用侧泛型则不会
声明侧泛型主要指以下内容
1.泛型类,或泛型接口的声明 2.带有泛型参数的方法 3.带有泛型参数的成员变量

使用侧泛型
也就是方法的局部变量,方法调用时传入的变量。

Gson解析时传入的参数属于使用侧泛型,因此不能通过Signature解析
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的泛型参数信息的特点。 通过匿名内部类实现了泛型参数的传递。

kotlin中的泛型

out TextView === ? extends TextView
out 意思就是我只能输出,不能输入,只能读我,不能写我

in TextView === ? super TextView
in 意思只能写不能读,因为不知道我具体的类型,


截屏2022-05-21 下午1.17.13.png

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

推荐阅读更多精彩内容