最详细的Java泛型知识点

Java泛型

一、 泛型(Generic Programming)

作用:编写的代码可以被很多不同类型的对象所重用

  • 泛型类: ArrayList,HashSet,HashMap等
  • 泛型方法:Collections.binerySearch,Arrays.sort等
  • 泛型接口:List,Iterator等
  • 泛型的本质:参数化类型避免类型转换代码可复用
  • 同类:
    • C++的模板(Template)
    • C#的泛型

二、自定义泛型类

  • 泛型类设计

    • 具有泛型变量的类

    • 在类名后用<T>代表引入类型

      • 多个字母表示多个引入类型,如<T,U>等(对于字母选择的特殊说明:有些字母是约定俗成的,例如数据结构中的ArrayList<E>,E代表Element,HashMap<K,V>,K表示Key,V表示Value;自定义泛型类常量常用T,T表示Template)
      • 引入类型可以修饰成员变量/局部变量/参数/返回值
      • 没有专门的Template关键字

      示例:

      /**
       * <T>引入类型可以修饰什么
       */
      public class Demo1<T> {
          //成员变量
          private T data1;
          private T data2;
      
          //参数
          public Demo1(T data1,T data2){
              this.data1 = data1;
              this.data2 = data2;
          }
      
          //返回值
          public T getData1() {
              return data1;
          }
      }
      
  • 泛型类调用

    • 传入具体的类

      • Demo1<Integer> d1 = new Demo1<Integer>(1,2);
      • Demo1<Integer> d1 = new Demo1<>(1,2);

      示例:

      public class Demo2 {
          public static void main(String[] args) {
              Demo1<Integer> d1 = new Demo1<Integer>(1,2);
              int data1 = d1.getData1();
              int data2 = d1.getData2();
              System.out.println(data1 + "," + data2);
              
              Demo1<Integer> d2 = new Demo1<>(1,2);
              Demo1<Integer> d3 = getReverse(d2);
              System.out.println(d3.getData1() + "," + d3.getData2());
          }
      
          //交换两个数据的值
          //static后的第一个<T>是泛型方法制定的变量类型。 Demo1<T>代表的是返回值
          public static <T> Demo1<T> getReverse(Demo1<T> d) {
              return new Demo1<T>(d.getData2(),d.getData1());
          }
      }
      
  • 泛型方法

    • 具有泛型参数的方法

    • 该方法可以在普通类/泛型类中

    • 泛型方法的引入类型<T>位于修饰符后,返回类型前

      示例:

      /**
       * 泛型方法
       */
      class ArrayUtil{
          public static <T> T getMiddle(T...a){
              return a[a.length/2];
          }
      }
      
      public class Demo3 {
          //引入类型写在方法名的前面
          String s1 = ArrayUtil.<String>getMiddle("abc","def","ghi"); //def
          //可以从后面的参数进行推断,此方法不推荐
          Integer i1 = ArrayUtil.getMiddle(1,2,3);
          //只要不是全空的就行,否则无法推断是什么类型
          String s2 = ArrayUtil.getMiddle("abc","def",null);
          //error 会先寻找共同的超类(Number),在进行转型
          //Integer i2 = ArrayUtil.getMiddle(1,2.5f,3L);
      }
      
  • 泛型接口

    • 和泛型类相似,在类名后面加<T>

    • T用来指定方法的返回值和参数

    • 实现接口的时候,指定类型

    • T也可以是一个泛型类

      示例:

      /**
       * 泛型接口
       */
      
      //泛型接口
      interface Calculator<T>{
          public T add(T operator1,T operator2);
      }
      
      //泛型接口的子类
      //实现接口的时候,要指定类型
      class IntegerCalculator implements Calculator<Integer>{
          @Override
          public Integer add(Integer operator1, Integer operator2) {
              return operator1 + operator2;
          }
      }
      
      public class Demo4 {
          public static void main(String[] args) {
              //当普通类使用
              IntegerCalculator c1 = new IntegerCalculator();
              System.out.println(c1.add(1,2));
              //new出来的对象转型为父接口类型对象
              Calculator<Integer> c2 = new IntegerCalculator();
              System.out.println(c2.add(1,2));
          }
      }
      
  • 小结:

    • 泛型类:整个类被泛型化,包括属性和方法
    • 泛型方法:方法的返回值和参数类型被泛型化
    • 泛型接口:子类的方法被泛型化

三、泛型类型限定

  • 有时候,在特定场合下,需要对类进行限定(使用某些特定方法)

  • <T extends Comparable>约定T必须是Comparable的子类

  • extends固定,后面可以多个,用&拼接,如<T extends Comparable & Serializable>

  • extends限定可以有多个接口,但是只能一个类,且类必须排第一位

  • 逗号隔开多个参数,<T extends File&Clonceable,U extends Serializable>

    示例:

    /**
     * 泛型类型的限定
     */
    class Person implements Comparable{
        int age;
    
        public Person(int age) {
            this.age = age;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "age=" + age +
                    '}';
        }
        //通过年龄进行比较
        @Override
        public int compareTo(Object o) {
            Person p = (Person)o;
            if (this.age > p.getAge()){
                return 1;
            }
            if(this.age == p.getAge()){
                return 0;
            }
            return -1;
        }
    }
    
    public class Demo5 {
        public static void main(String[] args) {
            Person p1 = new Person(15);
            Person p2 = new Person(33);
            Person p3 = new Person(25);
            System.out.println(getMin(p1, p2, p3));
        }
    
        //因为要进行比较选出最小的,所以需要限定传入的泛型类继承Comparable接口
        static <T extends Comparable> T getMin(T...a){
            if(null == a || a.length <= 0){
                return null;
            }
            T min = a[0];
            for (int i=1;i<a.length;i++){
                if(min.compareTo(a[i]) > 1){
                    min = a[i];
                }
            }
            return min;
        }
    }
    
  • 泛型类之间的继承

    • pair<S>和pair<T>没有任何关系,无论S和T之间是什么关系
    • 泛型类可以扩展实现其他的类,如ArrayList<T>实现List<T>
  • 上限界定符(为了弥补上面的不足)

    • Pair<? extends S>,表示Pair能接受的参数类型是S自身或者子类

      • 只能Get/不能Set,编译器只能保证出来的类型,但是不保证放入的对象是什么类型

      • 例子(B、C是A的子类):

        Pair<? extends A>代表Pair<A>,Pair<B>,Pair<C>等
        因为 ? extends A,所以 getA()的时候肯定可以转型到A
        void setA(? extends A)	//未知具体的类型,错误,例如B放到C里面
        
        就像类与类之间通俗的例子:护士和学生都是人,但是护士不能强转成学生,学生也不能强转成护士
        
  • 下限界定符

    • Pair<? super S>,表示Pair能接受的参数类型是S自身或超类
      • 只能Set/不能Get,编译器保证放入的是S本身或超类,但是不保证取出来是什么类型
  • 泛型PECS原则

    • Producer Extends,Consumer Super
    • 要从泛型类读取类型T的数据,并且不能写入,可以使用**?extens通配符**;(Producer Extends,泛型类是生产者,往外输出东西)
    • 如果要想泛型类写入T类型数据,并且不需要读取,可以使用**?super通配符**;(Consumer Super,泛型类是消费者,往外增加东西)
    • 如果既想写入又想读出,那就不同通配符
  • 无限定类型的泛型

    • Pair<T>,原始类型
    • Pair<?>无限定通配符,表示任意类型
      • get方法:不能确定出来的是什么类型,只能赋值给Object

      • set方法:无法放入任何对象,甚至是Object

四、泛型实现的本质和约束

​ 泛型是JDK1.5之后引入的新特性,并且JDK的版本是向后兼容的,即低版本的class文件可以在高版本的JDK上运行,因此,JVM里面没有泛型对象,而是采用擦除技术只有普通的类和方法

  • 类型擦除

    擦除泛型变量,替换为原始类型.

    1. 无限定替换成Object
    • 例:

      Public class Pair<T>{
          private T first;
          
          public T getFirst(){
              return first;
          }
      }
      

      下面是替换后的:

      Public class Pair<Object>{
          private Object first;
          
          public Object getFirst(){
              return first;
          }
      }
      
    1. 有限定则为第一个类型
    • 例:

      Public class Pair<T extends Comparable & Serializable>{
          private T first;
          
          public T getFirst(){
              return first;
          }
      }
      

      替换后的:

      Public class Pair<Comparable>{
          private Comparable first;
          
          public Comparable getFirst(){
              return first;
          }
      }
      
    1. 类型擦除后,为了保证类型的安全性,需要自动进行类型转换
    • 例:

      泛型变量翻译:

      Fruit a = fruits.getFirst();
      //上面的语句会被翻译成下面的语句
      Object a1 = fruits.getFirst();	//获取出来是Object
      Fruit a = (Fruit)a1;		//转型
      

      泛型方法翻译:

      public void setFirst(T first){
          this.first = first;
      }
      //在编译以后,字节码中类型被擦除掉了,T变成了Object类型
      

      重载泛型方法翻译(自动桥方法)

      //父类中:Pair<T>类中有一个setFirst方法,传入的参数是T类型,经过翻译后,T会被替换成Object类型
      //我们将父类的这个方法看作是下面这样的形式:
      public setFirst(Object o){}
      
      //子类的方法是
      public setFirst(Integer i){}
      

      在子类变异后的class文件中会有两个setFirst方法,一个参数类型是Integer,一个参数类型是Object,参数类型为Object的方法会调用参数类型是Integer的方法。这样一来,如果调用这个方法的是子类对象,那么会调用参数类型为Integer的,如果是父类对象,会先调用参数类型为Object的,在通过父类的方法调用参数类型为Integer的。

  • 泛型的约束

    • 不能使用基本(8种)类型来实例化泛型(通俗的说T必须用对象实例化)
    • 运行时类查询只使用与原始类型
    • 不能创建参数化类型的数组
    • 可变参数警告
    • 不能实例化类型变量
    • 不能构造泛型数组

五、Java类型协变和逆变

  • 形式化定义:

    A、B是类型,f( . )表示类型转换,≤表示继承关系,如A≤B,表示A继承于B

    • f( . )是协变的,如果如A≤B,有f(A) ≤ f(B)
    • f( . )是逆变的,如果如A≤B,有f(B) ≤ f(A)
    • f( . )是不变的,当上述两种条件都不成立,则f(A)与 f(B)没有关系
    • f( . )是双变的,如果如A≤B,有f(A) ≤ f(B)与f(B) ≤ f(A)同时成立
  • Java数据类型变化

    1. Java数组是协变的String是Object的子类,String[]是Object[]的子类

    2. Java的(原始的)泛型是不变的List<String>List<Object>没有关系

    3. 泛型可以采用通配符,支持协变和逆变(PECS原则)

      //A B C三类 B继承A C继承B
      ArrayList<? extends A> list1 = new ArrayList<B>();	//协变
      ArrayList<? super B> list1 = new ArrayList<B>();	//逆变
      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

生命中有太多不确定

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值