乐趣区

关于java:Java泛型总结

泛型总结

概述

  • 对于泛型,最基本的了解就是:泛型提供了编译时类型平安检测机制,该机制容许程序在编译时检测到非法的类型,以保障类型平安。泛型的实质是参数化类型,在面向对象编程的语言中,容许程序员在强类型校验下定义某些可变局部,以达到代码复用的目标

    • 强调的是 编译期间 的查看,如果在运行期间进行操作,比方应用反射是能够绕过泛型的编译查看的,当然最初可能就会引入一些系列的类型不兼容的谬误

泛型劣势

  • 解耦类型,比方一个类、接口或者办法要兼容多种数据类型的场景,晋升代码可重用性
  • 保障类型平安,防止类型转换谬误

    • 比方泛型与汇合的联结应用,防止了汇合充斥各种数据类型的数据最终不可避免的导致数据转换异样的状况产生
  • 晋升可读性,从编码阶段就明确某个类或者办法要解决的对象类型是什么

泛型原理

  • Java 的泛型是 伪泛型 ,这是因为 Java 在编译期间,所有的泛型信息都会被擦掉 (Java 编译器生成的字节码是不包涵泛型信息的),这也就是通常所说 类型擦除

    • 所谓的类型擦除就是将普泛型 T 改为 Object,设置了限定通配符 extends 的,擦除为类型下限,设定了限定通配符 super 的被擦除为 Object,应用 ? 作为通配符的同样被擦除为 Object
    • 伪泛型之伪在于泛型不能代表任意类型,比方说泛型不能够是根本数据类型(留神,泛型能够是数组类型 ),C++、C# 都实现了真正的泛型,而 Java 只在编译层面应用泛型信息,查看完就擦除了, 对于 JVM 来说泛型实质上就是 Object 类型
    • Java 的伪泛型实现了 最大的兼容性 ,相比与其余真泛型的语言来说,最直观的体验就是 即使源码中定义了泛型,然而实际上泛型可用可不必,与 Java5 的晚期版本也进行了兼容

      • 实际上如果应用 IDEA 编程时,如果提供了泛型然而不应用泛型的话,会有Raw use of parameterized class 'Class 名' 提醒,用来提醒应用泛型以取得应用泛型的劣势
    • 对于泛型类型擦除的误会:

      • 既然编译器将泛型擦除了,那么为什么在运行时还能通过反射取得泛型信息呢,这是因为,上边说的泛型擦除实际上是将代码中用到的泛型替换为 Object,然而在特定的状况下,泛型作为函数或者类的申明的一部分,会被当做是元信息存储,并能够在反射时应用,留神此时只是取得了编译时即确定的泛型信息,然而泛型运行时的具体类型依然无奈取得(运行时并不存在泛型,曾经被擦除),能够参考 Java 类型零碎和 Java 反射的了解

        • 这种反射对于获取泛型信息的反对能够看做是 JVM 对于 Java 泛型擦除失落类型信息的一种补救
    • 这么看 Java 的泛型就像是 语法糖。这种实现形式的益处在于不用批改JVM,缩小了潜在改变带来的危险
  • 因为 Java 引入了泛型,所以,只用 Class 来标识类型曾经不够了。实际上,Java 的类型系统结构如下

    • 对于 Java 泛型零碎可参考 Java 类型零碎
  • 对于泛型擦除的了解还能够参考下边的案例进行了解

    // 这是咱们本人编写的泛型
    public class Pair<T> {
      private T first;
      private T last;
      public Pair(T first, T last) {
        this.first = first;
        this.last = last;
      }
      public T getFirst() {return first;}
      public T getLast() {return last;}
    }
    
    // 应用泛型的时候的代码
    Pair<String> p = new Pair<>("Hello", "world");
    String first = p.getFirst();
    String last = p.getLast();
    
    // 编译之后的代码
    Pair p = new Pair("Hello", "world");
    String first = (String) p.getFirst();
    String last = (String) p.getLast();
    
    // 对应的字节码
     0 new #2 <com/diego/genericType/Pair>
     3 dup
     4 ldc #3 <Hello>
     6 ldc #4 <world>
     8 invokespecial #5 <com/diego/genericType/Pair.<init> : (Ljava/lang/Object;Ljava/lang/Object;)V>
    11 astore_1
    12 aload_1
    13 invokevirtual #6 <com/diego/genericType/Pair.getFirst : ()Ljava/lang/Object;>
    16 checkcast #7 <java/lang/String>
    19 astore_2
    20 aload_1
    21 invokevirtual #8 <com/diego/genericType/Pair.getLast : ()Ljava/lang/Object;>
    24 checkcast #7 <java/lang/String>
    27 astore_3
    28 return
    
    
    Pair<String> pair1 = new Pair<>("li","jia");
    Pair<String> pair2 = new Pair<>("li","jia");
    // JVM 只晓得有 Pair 类型的对象,而不意识泛型
    System.out.println(pair1.getClass() == pair2.getClass()); // true
    • 从字节码中一窥泛型到底时,能够看到泛型 T 全副擦除为 Object 类型,同时在返回类型为泛型的办法中,还有一个非凡的指令 checkcast 即执行类型强转,因为泛型的应用保障了返回值必然是指定类型,所以类型强制转化(先向上晋升为 Object,再还原回来)肯定胜利,如果不匹配则抛出 ClassCastException 运行时异样(正确应用泛型则不会呈现该异样)
    • 能够总结道:泛型起作用的过程除了编译时的平安类型查看外还包含,在泛型变量与确定类型的变量做赋值的地位会做平安的强制类型转换(得益于编译过程中的类型一致性查看)
  • 把泛型间接看做是 Object 类也是不对的,参考下边的例子了解

    // 实例化泛型的需要
    public class Pair<T> {
      private T first;
      private T last;
      public Pair() {
        // Compile error:
        first = new T();
        last = new T();}
      
      // 应用泛型重写 equals 办法时的难堪
      public boolean equals(T t) {// ....}
    }
    • 当咱们有在类中实例化泛型的需要的时候,是不能间接应用 new 进行实例化的 ,因为如果间接这样做可行的话,编译器会将其转换为创立一个 Object 对象,那么在应用泛型的时候(毕竟泛型是由用户指定的任意援用类型),不能进行强转,否则会呈现类型强转谬误, 正确的做法是依赖内部的参数

      public class Pair<T> {
        private T first;
        private T last;
        public Pair(Class<T> clazz) {first = clazz.newInstance();
          last = clazz.newInstance();}
      }
      
      // 这是模块中很常见的伎俩
      Pair<String> pair = new Pair<>(String.class);
    • equals 办法实际上在擦拭后与父类 Object 的 equals 办法形成重写,造成了事实上的歧义,因而实际上定义这样的办法时会间接报错,应将泛型 T 改为 Object 以实现重写,或者更改办法名不进行重写的尝试

Java 泛型的局限

  1. <T>不能是根本类型,例如 int,因为理论类型是ObjectObject 类型无奈持有根本类型
  2. 无奈获得带泛型的Class

    Pair<String> p1 = new Pair<>("Hello", "world");
    Pair<Integer> p2 = new Pair<>(123, 456);
    Class c1 = p1.getClass();
    Class c2 = p2.getClass();
    System.out.println(c1==c2); // true
    System.out.println(c1==Pair.class); // true
  3. 无奈判断带泛型的类型

    Pair<Integer> p = new Pair<>(123, 456);
    // Compile error:
    if (p instanceof Pair<String>) {}
    • 并不存在Pair<String>.class,而是只有惟一的Pair.class
  4. 不能应用 new 实例化 T 类型以及 T 类型数组

泛型的应用

罕用的泛型符号

  • 罕用的通配符为:TEKV?仅仅是约定俗成的符号而已,就跟参数的名字一样

    • T (type) 示意具体的一个 Java 类型
    • K V (key value) 别离代表 java 键值中的 Key Value—— 罕用于双泛型
    • E (element) 代表 Element,用于示意汇合中的元素
    • 示意不确定的 Java 类型,或者说是任意的 Java 类型
  • 再次强调,泛型自身是 Java 数据类型的代号,而不是数据类型自身,比方下边的例子

    public class GenericTypeTest<T> {<String, T, Alibaba> String get(String string, Alibaba alibaba) {return string;}
    
        public static void main(String[] args) {GenericTypeTest<Integer> test = new GenericTypeTest<>();
            Integer s = test.get(123, 123);
        }
    
    }
    • String 与 Alibaba 都是具体类型的一个代表符号,而这个具体类型是什么由函数调用方传入的参数的理论类型定义

泛型通配

  • 所谓通配即是指定泛型能够代表一系列的类型,以晋升泛型的应用效率,然而如果放开接管任意类型的话,则失去了泛型的类型限度作用,因而引入了以下两种可接管多种受泛型束缚的类型的泛型模式:

    • < ? extends T > 确保泛型必须是 T 的子类来设定泛型的上界,实用于生产汇合元素的场景,进一步讲就是适宜只读数据场景,比方计算汇合中所有元素的和

      int sumOfList(List<? extends Integer> list) {
        int sum = 0;
        for (int i=0; i<list.size(); i++) {Integer n = list.get(i);
          sum = sum + n;
        }
        return sum;
      }
    • < ? super T >确保类型必须是 T 的父类来设定泛型的下界,实用于增加汇合元素的场景,进一步讲就是适宜写数据场景,比方一个简略的 set 办法

      void set(Pair<? super Integer> p, Integer first, Integer last) {p.setFirst(first);
          p.setLast(last);
      }
    • 最完满的展现 extends 通配符和 super 通配符应用的例子就是 Java 规范库的 Collections 类定义的 copy() 办法:

      public class Collections {
          // 把 src 的每个元素复制到 dest 中:
          public static <T> void copy(List<? super T> dest, List<? extends T> src) {for (int i=0; i<src.size(); i++) {T t = src.get(i);
                  dest.add(t);
              }
          }
      }
  • 泛型通配的利用实际上能够分为代表 泛型类型匹配 与代表 泛型汇合的个性 两局部
extends
  • 能够向 <? extends T> 束缚的汇合援用 赋值任何 T 及 T 子类的汇合

    • 留神反过来是不容许的
  • 容许从汇合中 数据,然而 数据都会被强制转为类型 T 限度写 数据,只容许写入 null。以下边的代码为例:

    Pair<? extends Number, ? extends Number> pair = new Pair<>(1,2);
    // Integer key = pair.getKey(); // 编译时类型转换报错,实际上是 Integer key = (Number)pair.getKey();
    • 类型强转的限度是有情理的,如果汇合中存储有 Double 类型的数据话,能够应用 Integer 去承接吗,显然不适合,应用 Number 类型的援用承接更正当,也能够了解为 汇合中的子类数据取出时类型被擦除了
    • 同理,Pair<? extends Number>持有的类型都是 Number 或者 Number 的子类,然而又不能确定到底是哪个类型因而只能传入人畜有害的 null
super
  • 能够向 <? super T> 束缚的汇合援用 赋值任何 T 及 T 的父类汇合

    • 留神反过来是不容许的
  • 容许向汇合中 数据,然而 数据类型只能是 T 或者是 T 类型的子类类型 ,能够 读数据 ,然而 类型全副失落,只能返回 Object 类型的数据。以下边的代码为例:

    Pair<? super Cat> pair = new Pair<>();
    pair.setFirst(new Cat()); // 编译通过
    pair.setFirst(new Garfield()); // 编译通过
    // pair.setFirst(1); // 编译谬误
    Object first = pair.getFirst(); // 编译通过
    • 写数据时,只能写入类型 T 或者其子类,这样能够 放弃汇合内的类型兼容,如果能够写 T 的某个父类类型或者其余任意类型,就很难放弃类型兼容
    • 因为能够向受约束的援用赋值 T 以及 T 的 任意父类 的汇合,所以取数据时 间接丢掉类型以放弃类型兼容 ,当然也能够了解为 类型信息间接被擦除掉了
PECS 准则
  • 对于什么时候用 extends,什么时候用 super 的问题,《Effective Java》书中给出了一个论断即 PECS 准则producer-extends, consumer-super

    • 要从泛型类取数据时,用 extends
    • 要往泛型类写数据时,用 super
    • 既要取又要写,就不必通配符(即 extends 与 super 都不必)
多重限定
  • 类型参数能够应用多重限定,即应用 & 来示意,指定泛型 T 必须是 A 和 B 这两个接口的独特实现类

    public class Multilimit implements MultiInterfaceA ,MultiInterfaceB {public static <T extends MultiInterfaceA & MultiInterfaceB> void test (T t) {}}
    • 与汇合中的泛型利用做辨别

应用案例

一般泛型的应用
  • 以下边的代码为例,了解个别的泛型的应用

    public class ListNoGeneric {public static void GenericTest() {
            // 1. 汇合类中不应用泛型
            List a1 = new ArrayList();
            a1.add(new Object());
            a1.add(new Integer(111));
            a1.add(new String("hello a1a1"));
    
            // 2. 把 a1 援用赋值给 a2,留神 a2 的类型中引入了泛型 <Object>
            List<Object> a2 = a1;
    
            a2.add(new Object());
            a2.add(new Integer(222));
            a2.add(new String("hello a2a2"));
    
            // 3. 把 a1 援用赋值给 a3,留神 a3 的类型中引入了泛型 <Integer>
    
            List<Integer> a3 = a1;
            a3.add(new Integer(333));
            // 下边两行代码编译谬误
            //a3.add(new Object());
            //a3.add(new String("hello a3a3"));
    
            // 4. 把 a1 援用赋值给 a4,留神区别在于 a4 的类型引入了通配符
            List<?> a4 = a1;
            a4 = a3;
    
            a4.remove(0);
            a4.clear();
    
            // 编译出错,不容许增加任何元素
            //a4.add(new Object());
            // 容许增加 null
            a4.add(null);
            // 读数据时,失落类型信息,间接擦除为 Object
            Object o = a4.get(0);
        }
    }
    • 第一段代码演示了泛型推出之前的汇合的应用,各种类型能够一股脑的放到汇合中,然而取出应用时则要小心的校验类型,否则会呈现类型转换谬误
    • 第二段引入了 Object 作为泛型类型,此时仍能够增加各种类型的数据,因为 都是兼容 Object 的类型

      • 同时也展现了 Java 中泛型的 向前兼容,即非泛型汇合能够赋值给任何泛型限度的汇合 ,只管 Java 泛型放弃了对历史代码的兼容, 然而这种兼容很容易引入 BUG,参考下边的代码,将来还是要尽量应用泛型定义

        /**
         * 演示泛型的类型兼容引入的 BUG
         */
        public class TypeCompatibility {public static void main(String[] args) {JSONObject jsonObject = JSONObject.parseObject("{\"level\":[\"3\"]}");
                List<Integer> intList = new ArrayList<>(10);
        
                if (jsonObject != null) {intList.addAll(jsonObject.getJSONArray("level"));
                    int amount = 0;
                    for (Integer i : intList) {
                        // ClassCastException: String cannot be cast to Integer
                        amount = amount + i;
                    }
                }
            }
        }
        • adAll 办法的办法申明是:boolean addAll(Collection<? extends E> c);,而 JSONArray 类型的定义是public final class JSONArray extends AbstractJSON implements JSON, List,因为泛型兼容,所以增加胜利,然而理论增加的不是 Integer 类型的数据而是 String 类型,最终导致呈现强转异样

          • 实际上在 fastjson(1.2.79)中 JSONArray 的类型定义曾经更改为 public class JSONArray extends JSON implements List<Object>, Cloneable, RandomAccess, Serializable,此时类型查看起作用,addAll 处间接提醒类型不兼容, 这里体现了 ListList<Object>的不同
    • 第三段代码也展现了 Java 泛型的兼容性,如果将不带泛型的 a1 改为 List<Object>,则第三段代码的赋值就会编译出错, 反过来同样会编译谬误 ;与之比照的是, 如果数组这样赋值则不会出错,因为数组是协变的(所谓协变的概念可参考 Java 类型零碎)

      List<Object> b = new ArrayList<>();
      // List<Integer> c = b; // java.util.List<java.lang.Object> 无奈转换为 java.util.List<java.lang.Integer>
      List<Integer> c = new ArrayList<>();
      // List<Object> d = c; // java.util.List<java.lang.Integer> 无奈转换为 java.util.List<java.lang.Object>
      
      Integer[] integers = new Integer[]{1,2,3};
      Object[] objects = integers; // 可转换
      
      Object[] objs = new Object[3];
      // Integer[] ins = objs; // java.lang.Object[]无奈转换为 java.lang.Integer[]
      • 确定的泛型类型(即未应用通配符的泛型类型)是没有协变性的
    • 第四段代码阐明,当应用 ? 作为泛型通配符时,示意该泛型类型的援用能够指向任何泛型类型的汇合,然而不能增加任何元素,读出的数据也会失落类型信息,然而能够执行 remove 或者 clear,比方如果在上述代码中应用a4 = a3 也会失常编译通过。然而 不要误以为应用 ? 做通配符时能够容许任意类型的数据增加到汇合中,如果这样的话,泛型实际上失去了类型爱护的意义,应用 ? 做泛型的汇合个别作为参数接管内部的汇合或者返回一个不明确元素类型的汇合。这种性质在自定义的应用泛型的类中是一样的:

      public class Pair<T> {
      
          private T first;
          private T last;
      
          public Pair() {}
      
          public Pair(T first, T last) {
      
              this.first = first;
              this.last = last;
          }
      
          public T getFirst() {return first;}
      
          public T getLast() {return last;}
      
          public void setFirst(T first) {this.first = first;}
      
          public void setLast(T last) {this.last = last;}
      
          public static void main(String[] args) {Pair<?> pair = new Pair<>();
               //pair.setFirst(1); // 增加失败
              // pair.setLast(2); // 增加失败
      
              pair = new Pair<Integer>(); // 编译通过
              Pair<?> pair1 = new Pair<>(1,2); // 编译通过
          }
      
      }
      • 大多数状况下,能够引入泛型参数 <T> 打消 <?> 通配符
      • 实际上 <?> 等价于<? extends Object>
泛型限定符的应用
  • 以下边的代码为例,了解泛型限定符的应用

    public static void GenericWithLimitTest() {
            // 首先申明三个顺次继承的类
            List<Animal> animals = new ArrayList<>();
            List<Cat> cats = new ArrayList<>();
            List<Garfield> garfields = new ArrayList<>();
    
            animals.add(new Animal());
            cats.add(new Cat());
            garfields.add(new Garfield());
    
            // 测试赋值操作
            // 上行编译出错,List<? extends Cat> 类型的汇合只承受 Cat 或者其子类的汇合
            List<? extends Cat> extendsCat = animals;
            List<? super Cat> superCat = animals;
    
            extendsCat = cats;
            superCat = cats;
    
            extendsCat = garfields;
            // 上行编译出错,List<? super Cat> 类型的汇合只承受 Cat 或者其父类的汇合
            superCat = garfields;
    
    
            // add 办法测试
            // 泛型中应用 extends 关键字的汇合,只容许写 null
            extendsCat.add(new Animal());
            extendsCat.add(new Cat());
            extendsCat.add(new Garfield());
            extendsCat.add(null);
    
            // 泛型中应用 super 关键字的汇合,只容许写 Cat 类型数据或者其子类
            superCat.add(new Animal());
            superCat.add(new Cat());
            superCat.add(new Garfield());
    
    
            // get 办法测试,应用 extends 关键字时,读出的数据会被转型为 Cat 类型
            Cat cat = extendsCat.get(0);
            Object cat1 = extendsCat.get(0);
            // 上行编译出错,Cat 类型的值向其子类援用赋值会呈现类型强转失败
            Garfield cat2 = extendsCat.get(0);
    
            // 应用 super 关键字时,能够读数据,然而数据类型失落
            Object object = superCat.get(0);
            // Object 类型的对象不能被赋值到其子类的援用
            Cat cat3 = superCat.get(0);
    
        }
反射 API 对泛型的反对
  • Java 的局部反射 API 反对泛型

    // compile warning:
    Class clazz = String.class;
    // 必须强转,否则会编译失败
    String str = (String) clazz.newInstance();
    
    // no warning:
    Class<String> clazz = String.class;
    String str = clazz.newInstance();
    • 调用 ClassgetSuperclass()办法返回的 Class 类型是Class<? super T>

      Class<? super String> sup = String.class.getSuperclass();
    • 构造方法 Constructor 也反对泛型:

      Class<Integer> clazz = Integer.class;
      Constructor<Integer> cons = clazz.getConstructor(int.class);
      Integer i = cons.newInstance(123);
带泛型数组
  • 能够申明带泛型的数组,但不能用 new 操作符创立带泛型的数组:

    Pair<String>[] ps = null; // ok
    Pair<String>[] ps = new Pair<String>[2]; // compile error!
  • 必须通过强制转型实现带泛型的数组

    Pair<String>[] ps = (Pair<String>[]) new Pair[2];
    • 因为须要强制转换,所以咱们不得不首先创立一个一般的数组,如果不是间接应用 new 来创立这个一般的数组,而是应用了一个变量来援用这个一般数组的话,可能会导致谬误,这是因为 编译器会查看带泛型的数组,然而不会查看一般的数组,然而通过强制转型之后,两个数组援用实际上指向同一个数组,不被查看的一般数组可能会引入不合乎泛型的成员,从而导致谬误

      Pair[] arr = new Pair[2];
      Pair<String>[] ps = (Pair<String>[]) arr;
      
      ps[0] = new Pair<String>("a", "b");
      arr[1] = new Pair<Integer>(1, 2);
      
      // ClassCastException:
      Pair<String> p = ps[1];
      String s = p.getFirst();
  • 带泛型的数组在泛型擦除后实际上就是一般的数组类型

    Pair[] arr = new Pair[2];
    Pair<String>[] ps = (Pair<String>[]) arr;
    System.out.println(ps.getClass() == Pair[].class); // true
  • 在反对泛型的办法中创立泛型数组时不能间接 new T[],因为这样实际上创立的是Object[] 类型的数组,正确的创立办法与创立泛型实例的办法统一:引入 Class 实例

    public T[] create(Class<T> tClass) {return (T[])Array.newInstance(tClass, 5);
    }
泛型可变参数
  • 除了应用 newInstance 办法之外还能够应用 泛型可变参数 创立泛型数组

    public class ArrayHelper {
        @SafeVarargs
        static <T> T[] asArray(T... objs) {return objs;}
    }
    
    String[] ss = ArrayHelper.asArray("a", "b", "c");
    Integer[] ns = ArrayHelper.asArray(1, 2, 3);
  • 应用泛型可变参数存在肯定的危险

    public class ArrayHelper {static <K> K[] pickTwo(K k1, K k2, K k3) {return asArray(k1, k2);
        }
    
        static <T> T[] asArray(T... objs) {return objs;}
    
        public static void main(String[] args) {String[] arr = asArray("one", "two", "three");
            System.out.println(Arrays.toString(arr));
            // ClassCastException:
            String[] firstTwo = pickTwo("one", "two", "three");
            System.out.println(Arrays.toString(firstTwo));
        }
    }
    • 间接调用 asArray(T...) 仿佛没有问题,然而在另一个办法中,咱们返回一个泛型数组就会产生 ClassCastException,起因还是因为 类型擦除 pickTwo()办法外部,编译器无奈检测 K[] 的正确类型,因而返回了Object[]
    • 实际上 IDEA 会有正告,除非确认齐全没有问题,才能够用 @SafeVarargs 打消正告

  • 思考到上述呈现的对于泛型数组的问题,如果在办法外部创立了泛型数组,最好不要将它返回给内部应用
类型转换
  • 要明确一点:泛型只是提供一种编译时的类型查看机制,它并不是类、接口、办法的一部分,除此之外,确定的泛型类型(即未应用通配符的泛型类型)是没有协变性的

    // 创立 ArrayList<Integer> 类型:ArrayList<Integer> integerList = new ArrayList<Integer>();
    // 增加一个 Integer:integerList.add(new Integer(123));
    //“向上转型”为 ArrayList<Number>:赋值失败
    ArrayList<Number> numberList = integerList;

泛型的定义

Class

ArrayList 为例

// 类名前面要加一个 <T>, 其作用就是先申明这个泛型的符号(实质上就是个类型参数),尔后类中就能够应用此泛型符号作为个别的类型标记
public class ArrayList<T> {private T[] array;
  private int size;
  public void add(T e) {...}
  public void remove(int index) {...}
  public T get(int index) {...}
}
  • 应用 Java 类中的泛型的时候留神

    • 静态方法中不能应用类上定义的泛型 ,类上定义的泛型仿佛与实例对象 this 有关系,所以不能在动态语境中应用,如果非要应用,能够 应用定义在办法上的泛型(与类或者办法自身无关,只与泛型定义的地位无关),下边看实例代码

      public class Pair<T> {
        private T first;
        private T last;
        public Pair(T first, T last) {
          this.first = first;
          this.last = last;
        }
        public T getFirst() { ...}
        public T getLast() { ...}
      
        // 对静态方法应用 <T>: 编译不通过 this cannot be referenced from a static contex
        public static Pair<T> create(T first, T last) {return new Pair<T>(first, last);
        }
      }
      
      
      class Pair<T> {
        private T first;
        private T last;
        public Pair(T first, T last) {
          this.first = first;
          this.last = last;
        }
        public T getFirst() {return (T)first;
        }
        public T getLast() {return (T)last;
        }
      
        // 对静态方法应用 <K>: 对于一般的函数也是实用的
        public static<K> Pair<K> create(K first, K last) {return new Pair<K>(first, last);
        }
      }
      
      
      // 批改为应用办法上申明的泛型
      public class Demo {public static void main(String[] args) {Pair<String> pair = Pair.create("li","jia");
          Pair<Integer> pair1 = pair.create(1,2);
          System.out.println(pair.getFirst());
        }
      }
    • 一个不带泛型的类能够继承自带泛型的类

      class Pair<T> {
        private T first;
        private T last;
        public Pair(T first, T last) {
          this.first = first;
          this.last = last;
        }
        public T getFirst() {return (T)first;
        }
        public T getLast() {return (T)last;
        }
        public <K> Pair<K> create(K first, K last) {return new Pair<K>(first, last);
        }
      }
      class IntPair extends  Pair<Integer> {public IntPair(Integer first, Integer last) {super(first, last);
        }
      }
      • 对于个别的泛型类型的实例,无奈通过其 Class 实例获取泛型,然而对于子类继承带泛型父类的场景或者是办法中应用了泛型等场景,均能够通过反射获取对应的泛型信息,以上边的 IntPair 类为例

        Class<IntPair> clazz = IntPair.class;
        Type t = clazz.getGenericSuperclass();
        if (t instanceof ParameterizedType) {ParameterizedType pt = (ParameterizedType) t;
          Type[] types = pt.getActualTypeArguments(); // 可能有多个泛型类型
          Type firstType = types[0]; // 取第一个泛型类型
          Class<?> typeClass = (Class<?>) firstType;
          System.out.println(typeClass); // Integer
        }

Interface

  • 一个典型的案例就是 Comparable 接口

    interface Comparable<T> {int compareTo(T o);
    }
  • 对于带泛型的类、接口的继承与实现能够有两种模式,父类或者是接口能够是带通配符泛型的,也能够是具体类型的

    class IntPair<T> extends Pair<T>{// ...}
    
    class IntPair extends Pair<Integer>{// ...}
    
    class Person implements Comparable<Person> {
        String name;
        int score;
        Person(String name, int score) {
            this.name = name;
            this.score = score;
        }
        public int compareTo(Person other) {return this.name.compareTo(other.name);
        }
        public String toString() {return this.name + "," + this.score;}
    }
    • 实现带泛型接口的类必须实现正确的泛型类型

Function

  • 定义在函数上的泛型优先被被所在函数应用

    public static <E> void printArray(E[] inputArray )
    {for ( E element : inputArray){System.out.printf( "%s", element);
      }
      System.out.println();}
    • 泛型申明的地位在函数修饰符后,返回类型前

多泛型的应用

  • 以上三种地位的泛型都能够应用多泛型,典型的例子就是 Map 的定义

    public class Pair<T, K> {
      private T first;
      private K last;
      public Pair(T first, K last) {
        this.first = first;
        this.last = last;
      }
      public T getFirst() { ...}
      public K getLast() { ...}
    }
    
    Pair<String, Integer> p = new Pair<>("test", 123);

嵌套泛型的应用

  • 所谓嵌套泛型的含意就是泛型代表的类型也应用了泛型

    public class Subset {public List<List<Integer>> subsets(int[] nums) {
            // 对于蕴含泛型的类型,保障内层泛型统一即可
            LinkedList<List<Integer>> res = new LinkedList<>();
            LinkedList<Integer> track = new LinkedList<>();
            backtrack(0, nums, res, track);
            return res;
        }
    
        private void backtrack(int i, int[] nums, LinkedList<List<Integer>> res, LinkedList<Integer> track) {res.push(new ArrayList<>(track));
            for (int j = i; j < nums.length; j++) {track.push(nums[j]);
                backtrack(j + 1, nums, res, track);
                track.pop();}
        }
    }
    • 留神 res 的类型,只有内部的理论类型能够思考转型,即 LinkedList 转到 List,外部的泛型放弃齐全的统一( 必须是齐全的统一,泛型不存在什么转型)即可
    • 对于 LinkedList<List<Integer>> 外部的泛型在 res.push 起作用,ArrayList能够降级为List

      • res.push(new ArrayList<>(track))这一句中,会查看 track 的泛型类型,如果应用的是 ArrayList 的空参数构造函数也是容许的,如果 track 泛型的类型不统一,IDE 会提醒无奈猜想 ArrayList 的泛型类型

参考

  • 《码出高效:Java 开发手册》
  • 廖雪峰 Java 教程
  • Java 中的逆变与协变
退出移动版