JAVA的泛型

1.概述
  • 泛型,即"参数化类型"。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递参数。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参)。然后在使用/调用时传入具体的类型(类型实参)

  • 泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为了一个参数,这种参数类型可以用在接口方法中,分别被称为泛型类泛型接口泛型方法

  • 代码演示

    List arrayList = new ArrayList();
    arrayList.add("aaaa");
    arrayList.add(100);
    
    for(int i = 0;i < arrayList.size() ;i++){
        String item = (String)arrayList.get(i);
        Log.d("泛型测试""item = "+ item)
    }
    
    • 毫无疑问,程序的运行结果以崩溃结束

      java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
      
    • ArrayList可以存放任意类型,为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。

      List<String> arrayList = new ArrayList<String>();
      ....
      //arrayList.add(200);在编译阶段,编译器就会报错
      
2.特性
  • 泛型只在编译阶段有效

    List<String> stringArrayList = new ArrayList<String>();
    List<Integer> integerArrayList = new ArrayList<Integer>();
    
    Class classStringArrayList = stringArrayList.getClass();
    Class classIntegerArrayList = integerArrayList.getClass();
    
    if(classStringArrayList.equals(classIntegerArrayList)){//true
        System.out.println("泛型测试:类型相同");
    }
    
    • 通过上面的例子可以证明,在编译之后程序会采取泛型化的措施,也就是Java中的泛型,只在编译阶段有效,在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查类型转换的方法,也就是说,泛型信息不会进入到运行时阶段
    • 对此总结成一句话:泛型类型在逻辑上看似是多个不同的类型,实际上都是相同的基本类型
3.泛型的使用
  • 泛型有三种使用方式,分别为:泛型类泛型接口泛型方法
3.1泛型类
  • 泛型类型用于类的定义中,被称为泛型类,通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map

    泛型类的最基本写法

    class 类名称<泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
        private 泛型标识 /*成员变量类型*/ param;
        ....
        
    }
    
  • 实例

    • 举例普通的泛型类

      /**
       *
       * 此处T可以随便写为任意标识,常见的如T E K V等形式的参数常用于表示泛型
       * 在实例化泛型类时,必须指定T的具体类型
       */
      public class Generic<T> {
          //key这个成员变量的类型为T,T的类型由外部指定
          private T key;
      
          public Generic(T key){
              //泛型构造方法形参key的类型也为T,T类型由外部指定
              this.key = key;
          }
          public T getKey(){
              //泛型方法getKey的返回值类型为T,T的类型由外部指定
              return key;
          }
      }
      
      /*
      泛型的类型参数只能时类类型(包括自定义类),不能时简单的类型
      传入的实参类型与泛型的类型参数相同,即为Integer
       */
      Generic<Integer> integerGeneric = new Generic<Integer>(123456);
      
      Generic<String> stringGeneric = new Generic<String>("key_value");
      
      System.out.println(integerGeneric.getKey());//123456
      System.out.println(stringGeneric.getKey());//key_value
      
      ####注意##############
          //泛型的类型参数只能是类类型,不能是简单类型
          //不能对确定的泛型类型使用instanceof操作,如下面的操作是非法的,编译时会出错
          if(ex_num instanceof Generic<Number>){
              
          }
      
3.2泛型接口
  • 泛型接口和泛型类的定义及使用基本相同,泛型接口常被用在各种类的生产器中

    //定义一个泛型接口
    public interface Generator<T>{
        public T next();
    }
    
  • 当实现泛型接口的类,未传入泛型实参时

    /**
     * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
     * 即:class FruitGenerator<T> implements Generator<T>
     * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:Unknown class
     */
    class FruitGenerator<T> implements  Generator<T>{
        @Override
        public T next(){
            return null;
        }
    }
    
3.3泛型方法
  • 在java中,泛型类定义非常简单,但是泛型方法就比较复杂

    //尤其是我们见到的大多数泛型类中的成员方法都使用了泛型,有些甚至泛型类中也包含泛型方法。
    
  • 泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型

    /**
     *泛型方法的基本介绍
     * @param tClass 传入的泛型类
     * @param <T>   T 返回值为T类型
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     * 
     * 说明:  
     *      1)public与返回值中间<T>非常重要,可以理解为声明此方法为泛型方法
     *      2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法
     *      3)<T>声明该方法将使用泛型类型T,此时才可以在方法中使用类型T
     *      4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T K E V等形式的参数用于表示泛型
     */
    
    public <T> T genericMethod(Class<T> tClass) throws IllegalAccessException, InstantiationException {
        T instance = tClass.newInstance();
        return instance;
        
    }
    
  • 泛型方法的基本用法

    public class GenericTest {
        //泛型类
        public class Generic<T>{
            private T key;
    
            public Generic(T key) {
                this.key = key;
            }
    
            /**
             * 该方法中使用了泛型,但是这并不是一个泛型方法,这只是类中一个普通的成员方法,只不过
             * 它的返回值是在声明泛型类已经声明过的泛型,所以该方法可以继续使用T这个泛型
             * @return
             */
            public T getKey() {
                return key;
            }
    
            /**
             * 这个方法显然是有问题的,在编译器会给这样的报错信息:cannot reslove symbol E
             * 因为在这个类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别
             * @param key
             *
             * public void setKey(E key) {
             *             this.key = key;
             *         }
             */
    
            /**
             * 这个才是真正的泛型方法
             * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
             * 这个T可以出现在这个泛型方法的任意位置
             * 泛型的数量可以为任意多个
             *  public <T,K> K showKeyName(Generic<T> container){}
             * @param container
             * @param <T>
             * @return
             */
            public <T> T showKeyName(Generic<T> container){
                System.out.println("container key:"+container.getKey());
                //不恰当,只为说明泛型的特性
                T test = container.getKey();
                return test;
            }
    
            /**
             * 这是一个普通方法,只是使用了Generic<Number>这个泛型做了形参而已
             * @param obj
             */
            public void showKeyValue1(Generic<Number> obj ){
                System.out.println(obj.getKey());
            }
    
            /**
             * 这也是一个普通方法,只不过使用了?泛型通配符
             * @param obj
             */
            public void showKeyValue2(Generic<?> obj){
                System.out.println(obj.getKey());
            }
        }
    
    }
    
  • 类中的泛型方法

    • 当然这并不是泛型方法的全部,泛型方法可以出现在任何地方和任何场景中使用,但是有一种情况是非常特殊的,当泛型方法出现在泛型类中时。

      public class GenericFruit {
          class Fruit{
              @Override
              public String toString() {
                  return "fruit";
              }
          }
          class Apple extends Fruit{
              @Override
              public String toString() {
                  return "apple";
              }
          }
          class Person{
              @Override
              public String toString() {
                  return "person";
              }
          }
          class GenerateTest<T>{
              public void show(T t){
                  System.out.println(t.toString());
              }
      
              /**
               * 在泛型类中声明了一个泛型方法,使用了泛型E,这种泛型E可以为任意类型,可以类型与T相同,也可以不同
               * 由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明,编译器也能正常识别方法中识别的泛型
               * @param e
               * @param <E>
               */
              public <E> void show_1(E e){
                  System.out.println(e.toString());
              }
      
              /**
               * 在泛型类中声明一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型
               * @param t
               * @param <T>
               */
              public <T> void show_2(T t){
                  System.out.println(t.toString());
              }
          }
          public static void main(String[] args){
              GenericFruit genericFruit = new GenericFruit();
              Apple apple = genericFruit.new Apple();
              Person person = genericFruit.new Person();
      
              GenerateTest<Fruit> fruitGenerateTest = genericFruit.new GenerateTest<Fruit>();
              //apple是Fruit的子类,所以这里可以
              fruitGenerateTest.show(apple);//apple
              /**编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
               * fruitGenerateTest.show(person);
               */
              //使用这两个方法都可以成功
              fruitGenerateTest.show_1(apple);//apple
              fruitGenerateTest.show_1(person);//person
      
              //使用这两种方法都可以成功
              fruitGenerateTest.show_2(apple);//apple
              fruitGenerateTest.show_2(person);//person
          }
      }
      
  • 泛型方法和可变参数

    public <T> void printMsg(T... args){
        for(T t:args){
            System.out.println("泛型测试:"+t)
        }
    }
    
  • 静态方法与泛型

    • 静态方法有一种情况需要注意一下,那就是类中的静态方法使用方向:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上

    • 即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法

      public class StaticGenerator<T> {
          /**
           * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
           * 即使静态方法要使用泛型类中已经声明过的泛型也不可以
           * public static void show(T t){
           *         System.out.println("泛型"+t);
           *     }
           *
           * 编译器会提示错误信息:'Basic.annotation.StaticGenerator.this' cannot be referenced from a static context
           */
          public static <T> void show(T t){
              System.out.println("泛型:"+t);
          }
      }
      
  • 泛型方法的总结

    • 泛型方法能使方法独立于类而产生变化,一下使一个基本的指导原则:
      • 无论何时,如果你能做到,你就该尽量使用泛型方法,也就是说,如果使用泛型方法将整个类泛型化, 那么就应该使用泛型方法。
      • 对于一个static的方法,无法访问泛型类型的参数,所以static方法要使用泛型能力, 使其成为泛型方法
3.4泛型通配符
  • Integer是number的一个子类,同时在特性性章节中验证过Generic与Generic实际上是相同的一种基本类型,通过提示信息我们可以看到Generic不能被看作为Generic的子类,由此可以看出:同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类型实例是不兼容

    Generic<Number> numberGeneric = new Generic<Number>(12345678);
    Generic<Integer> integerGeneric = new Generic<Integer>(123456);
    /**
     * showKeyValue(integerGeneric);
     * 这个方法编译器会为我们报错:Generic<java.lang.Integer> cannot be applied to Generic<java.lang.number>
     */
    showKeyValue(numberGeneric);
    
    
    public static void showKeyValue(Generic<Number> obj){
        System.out.println(obj.getKey());
    }
    
  • 同一个方法,不能为了处理不同的类型而新建一个方法,需要一个逻辑上可以表示同时是Generic和Generic父类的引用类型,由此类型通配符应运而生

    /**
     * 类型通配符一般是使用?代替具体的类型实参,注意了,此处'?'是类型实参,而不是类型形参,
     * @param obj
     */
    public static void showKeyValue(Generic<?> obj){
        System.out.println(obj.getKey());
    }
    
4.泛型上下边界
  • 在使用泛型的时候,传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或者某种类型的子类

  • 泛型添加上边界,即传入的类型实参必须使指定类型的子类型

    public class Generstion<T> {
        public static void main(String[] args) {
            /**
             *前提是泛型类使用Generic<T extends Number>
             * Generic<String> stringGeneric = new Generic<String>("1111111");
             * 编译器会报错:String不是Number的子类
             */
    
            Generic<Integer> integerGeneric = new Generic<Integer>(2222);
            Generic<Float> floatGeneric = new Generic<>(1234.5f);
            Generic<Double> generic = new Generic<Double>(1234.56);
            /**
             *前提是泛型类使用Generic<T>
             * showKeyValue(stringGeneric);
             * 编译器会报错:String不是Number的子类
             */
    
            showKeyValue(integerGeneric);
            showKeyValue(floatGeneric);
            showKeyValue(generic);
            
        }
        public static <T> void showKeyValue(Generic<? extends Number> obj){
            System.out.println("泛型测试:"+obj.getKey());
        }
    }
    class Generic<T>{
        private T key;
    
        public Generic(T key) {
            this.key = key;
        }
    
        public T getKey() {
            return key;
        }
    }
    
  • 泛型方法中添加上下边界限制的时候,必须声明与返回值之间的上添加上下边界,即在泛型声明的时候添加

/**
 * 在泛型方法中添加上下边界限制的时候,必须声明与返回值之间的<T>上添加上下边界,即在泛型声明的时候添加
 * public <T> T showKeyName(Generic<T extends Number> container){};编辑器会报错:'Unexpected bound
 * @param container
 * @param <T>
 * @return
 */
public <T extends  Number> T showKeyName(Generic<T> container){
    System.out.println("container key:"+container.getKey());
    T test = container.getKey();
    return test;
}
  • 【总结】 泛型的上下边界添加,必须与泛型的声明在一起
5.泛型数组
5.1实例
public class GenericArray<T> {

    public static void main(String[] args) {
        List<String> string = new ArrayList<>();
        string.add(new String("123"));
        string.add(new String("1234"));
        /**
         * 不被允许创建
         * List<String>[] strings = new ArrayList<String>[10];
         */
        //通配符创建泛型数组是可以的
        List<?>[] ls = new ArrayList<?>[10];
        //这样创建也可以
        List<String>[] ls_2 = new ArrayList[10];
        /**
         * 由于jvm泛型的擦除机制,在运行时是不知道泛型信息的,所以oa[1]赋上一个ArrayList而不会出现异常,但是在取出数据
         * 的时候却要做一次类型转换,所以会出现ClassCastException,如果可以进行泛型数组的声明,上面说的这种情况在编译期将
         * 不会出现任何的警告和错误,只有在运行时才会出错,而对泛型数组的声明进行限制,对于这样的情况,可以在编译器提示代码有
         * 类型安全问题,比没有任何提示要强很多
         */
        Object o = ls;
        Object[] oa = (Object[]) o;
        oa[1] = string;
        /**
         * 数组类型不可以是类型变量,除非时采用通配符的方式;对于通配符的方式,最好取出的数据是要做显式的类型转换的
         */
        String o1 = (String) ls[1].get(1);
        System.out.println(o1);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值