【Java基础】- 泛型篇

简介

  • 说起各种高级语言,不得不谈泛型,当我们在使用java集合的时候,会发现集合有个缺点:把一个对象“丢进”集合之后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,改对象的编译类型就变成了Object类型

    • 问题1:集合对元素类型没有任何限制,这样可能会引发一些问题,比如创建一个用于保存A对象的集合,但不小心把B对象放进去,会引发异常
    • 问题2: 由于把对象放进去时,集合对视了对象的状态信息,集合只知道它盛装的是Object,因此去取集合元素后通常还需要进行强制类型装换,这个过程不仅增加了编程的复杂度,还可能引发CLassCastException异常
  • 为解决以上问题,便引入“泛型”

    • java 5以后,java引入了“参数化类型”的概念,允许程序在创建集合时指定集合元素的类型

    • java 7之前,如果使用带泛型的接口、类定义变量,那么调用构造器创建对象时构造器的后面也必须带泛型

      • 比如
      //java 7之前
      List<String> list = new ArrayList<String>();//后面的<String>是必须带上的
      //java 7之后,"菱形"语法
      List<String> list = new ArrayList<>();
      
      

      注:java 9允许在使用匿名内部类时使用菱形语法

    • 概念定义:允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方式动态地指定

    • 我们来看一下定义泛型接口、类

      /**
      * 定义泛型接口,实质:允许在定义接口、类时什么类型形参,
      * 类型形参在整个接口、类体内可当成类型使用,几乎所有可
      * 使用普通类型的地方都可以使用这种类型形参
      */
      public interface List<T> {
          void add(T x);
      }
      
      /**
      * 定义
      * 
      * 
      */
      @Data
      public class Clazz<T> {
          private T a;
          public Clazz(T a){
              this.a = a;
          }
      }
      
      //使用Clazz
      pulic void method(){
          Clazz<String> clazz = new Clazz<>("");
      }
      
    • 从泛型类派生子类

      • 当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或从该父类派生子类,需要指出的是,当使用这些接口、父类时不能再包含泛型形参
      //定义类Son类继承Parent类
      public class Son extends Parenet<T>{
      }
      
      //使用Parent类时为T形参传入String类型
      public class Son extends Parent<String>{
      }
      
      //使用Parent类时,没有为T形参传入实际的类型参数
      public class Son extends Parent{
      }
      

      像这种使用Parent类时省略泛型的形式被称为原始类型(raw type)
      如果从Parent<String>类派生子类,则在Parent类中所有使用T类型的地方都将被替换成String类型

    • 并不存在泛型类

      • List<String>List<Integer> 创建出来的是同样class文件,它们在运行时总有同样的类,故在静态方法、静态初始化块或者静态变量的生命和初始化中不允许使用泛型形参
      public class R<T>{
          //错误,不能在静态变量声明中使用泛型形参
          static T info;
          //错误,不能再静态方法声明中使用泛型形参
          public void foo(T p){
          }
      }
      
    • 类型通配符

      • 定义:为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?>(意思是元素类型未知的List)。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型
      • 类型通配符的上限
        • 定义:当直接使用List<?>·这种形式时,即表明这个List集合可以是任何泛型List的父类。但还有一种特殊的情形,程序不希望这个List<?>`是任何泛型List的父类,只希望它代表某一类泛型List的父类
        //定义上限为Parent类,表示泛型形参必须是Parent子类
        List<? extends Parent>
        
        • 协变:对于更广泛的泛型类来说,指定通配符上限就是为了支持类型型变。比如Foo是Bar的子类,这样A<Foo>就相当于A<? extends Bar>的子类,可以将A<Foo>赋值给A<? extends Bar>类型的变量,这种型变方式被称为协变
      • 类型通配符的下限
        • 定义:通配符的下限用<? super类型>的方式来指定,通配符下限的作用与通配符上限的作用恰好相反
        //定义下限为Parent类
        List<? super Parent>
        
        • 逆变:比如Foo是Bar的子类,当程序需要一个A<? super Foo>变量时,程序可以将A<Bar>A<Object>赋值给A<? super Foo>类型的变量,这种型变方式被称为逆变

        对于逆变的泛型而言,它只能调用泛型类型作为参数的方法;而不能调用泛型类型作为返回值类型的方法。口诀是:逆变只进不出

    • 泛型方法

      • 定义:所谓泛型方法,就是在声明方法时定义一个或多个泛型形参,与类、接口中使用泛型参数不同的是,方法中的泛型参数无须显式传入实际类型参数
        修饰符<T,S>返回值类型 方法名(形参列表){
            //TODO
        }
        
      • 泛型方法和类型通配符的区别
        • 使用通配符比使用泛型方法(在方法签名中显式声明泛型形参)更加清晰和准确
        • 类型通配符既可以在方法签名中定义形参的类型,也可以用于定义变量的类型;但泛型方法中的泛型形参必须在对应方法中显式声明
        • 大多数时候都可以使用泛型方法来代替类型通配符
          //使用类型通配符
          public interface Collection<E>{
              void add(Collection<?> p);
              void delete(Collection<? extends E> p)
          }
          
          //使用泛型方法
          public interface Collection<E>{
              <T> void add(Collection<T> p);
              <T extends E> void delete(Collection<T> p)
          }
          
        • 也可以同时使用泛型方法和通配符
          public class Collections{
              public static <T> void copy(List<T> dest,List<? extends T> src){}
          }
          
    • “菱形”语法与泛型构造器

      • “菱形”语法前面已经提到,不再赘述,说一下啥是泛型构造器,其实就是java允许构造器签名中声明泛型形参
        class Foo{
            public <T> Foo(T t){
            }
        }
        
        public void method(){
            //泛型构造器中T类型为String
            new Foo("");
            //也可以这么定义,显示指定T类型为String
            new<String> Foo("");
            //泛型构造器中T类型为Integer
            new Foo(10);
        }
        
    • 泛型方法与方法重载

      • 因为泛型既允许设定通配符的上限,也允许设定通配符的下限,从而允许在一个类里包含以下两种方法的定义
        <T> void copy(Collection<T> des,Collection<? extends T> src){};
        <T> T copy(Collection<? super T> des,Collection<T> src){};
        
      • 重载的情况
        public void method(List<String> list){}
        public void method(List<Integer> list){}
        
        • 上述这段代码是不能被编译的,因为参数List<Integer>List<String>编译之后都被擦除了, 变成了同一种的裸类型List,类型擦除导致这两个方法的特征签名变得一模一样(下面会提到类型擦除)
    • 类型推断

      • java 8改进了泛型方法的类型推断能力,类型推断主要有如下两方面
        • 1)可通过调用方法的上下文来推断泛型的目标类型
        • 2)可在方法调用链中,将推断得到的泛型传递到最后一个方法
    • 泛型擦除和转换

      • 擦除:当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有在尖括号之间的类型信息都将被扔掉;Java代码编译成Class文件, 然后再用字节码反编译工具进行反编译后, 将会发现泛型都不见了, 程序又变回了Java泛型出现之前的写法, 泛型类型都变回了裸类型(List<String> 对应的裸类型就是List)
        • 比如:List<String> 类型会被转换成List,则该List对集合元素的类型检查变成了泛型参数的上限(Object),那么在使用,比如插入的时候,又会出现从Object到String的强制转型代码
        • 擦除法所谓的擦除, 仅仅是对方法的Code属性中的字节码进行擦除, 实际上元数据中还是保留了泛型信息, 这也是我们在编码时能通过反射手段取得参数化类型的根本依据
      • java不支持原生类型的泛型,即是不支持 int/long等,List<int>这种是不支持的,那么一旦把泛型信息擦除后,遇到原生类型时把装箱、 拆箱也自动做了,这也成为Java泛型慢的重要原因
    • 泛型与数组

      • 数组元素的类型不能包含泛型变量或泛型形参,除非是无上限的类型通配符,但可以声明元素类型包含泛型变量或泛型形参的数组。也就是说,只能声明List<String>[]形式的数组,但不能创建ArrayList<String>[10]这样的数组对象
  • 总结:Java的泛型在使用期间需要更加注意泛型擦除的情况,总体而言,其写法也并不优雅。也希望未来的泛型会支持基本类型。

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

推荐阅读更多精彩内容