一、泛型概述

在没有泛型之前,一旦把一个对象放到Java集合中,集合就会忘记对象的类型,把所有对象都当做Object类型处理。当程序从集合中取出对象后,就需要进行强制类型转换,这种类型转换不仅使代码臃肿,而且很容易引起ClassCastExeception异常。在Java5引入泛型机制后,在集合接口、类后增加一对尖括号,在括号里放一个数据类型,即表明这个集合接口、集合类只能保存特定类型的对象,在编译时检查集合中元素的类型,如果试图向集合中添加不满足要求类型的对象,编译器就会提示错误。增加泛型后,可以使程序更加简洁,程序更加健壮(Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastExeception异常)。
Java5以后,引入了“参数化类型(parameterized type)”的概念,允许程序在创建集合时指定集合元素的类型,Java的参数化类型被称为泛型(Generic)。
注:泛型只在编译时起作用,即使用了泛型后,对象的编译时类型改变,但实际运行时类型没有改变。

二、泛型入门

1.不使用泛型的缺点

  1. public class Test {
  2. public static void main(String[] args) {
  3. List mylist = new ArrayList();//创建List类型集合
  4. mylist.add("A");
  5. mylist.add(100);
  6. mylist.add(true);
  7. for (int i = 0; i < mylist.size(); i++) {
  8. Object o = mylist.get(i);
  9. String s = (String) o;//编译无问题,但运行时报错
  10. System.out.println(s);
  11. }
  12. }
  13. }
  14. //java.lang.ClassCastException异常
  1. //不使用泛型机制,分析程序存在的缺点
  2. class Animal{
  3. public void move(){
  4. System.out.println("移动...");//父类自带方法
  5. }
  6. }
  7. class Cat extends Animal{
  8. public void catchMouse(){
  9. System.out.println("猫抓老鼠...");//子类特有方法
  10. }
  11. }
  12. class Bird extends Animal{
  13. public void fly(){
  14. System.out.println("鸟儿在飞...");//子类特有方法
  15. }
  16. }
  17. public class Test {
  18. public static void main(String[] args) {
  19. //创建一个只想保存Animal对象的List
  20. List myList = new ArrayList();
  21. myList.add(new Cat());
  22. myList.add(new Bird());
  23. myList.add("A");
  24. myList.add(1);
  25. //还是可以把其他类型放进集合,因为不使用泛型时,
  26. //Java把集合元素都当作Object类型处理
  27. //遍历集合,取出每个Animal,让它move
  28. Iterator it = myList.iterator();
  29. while (it.hasNext()){
  30. //没有这个语法,通过迭代器取出的对象是Object类型
  31. //Animal obj = it.next();
  32. Object obj = it.next();
  33. //obj.move();obj里没有move()方法,不能直接调用,需要向下转型。
  34. if (obj instanceof Animal){
  35. Animal animal = (Animal) obj;
  36. animal.move();
  37. }
  38. }
  39. //遍历集合,取出Cat抓老鼠,Bird让它飞
  40. Iterator it1 = myList.iterator();
  41. while (it1.hasNext()){
  42. Object obj = it1.next();
  43. if (obj instanceof Cat){
  44. Cat c = (Cat) obj;
  45. c.catchMouse();
  46. }
  47. if (obj instanceof Bird){
  48. Bird b = (Bird) obj;
  49. b.fly();
  50. }
  51. }
  52. }
  53. }

2.使用泛型后改进的程序

  1. class Animal{
  2. public void move(){
  3. System.out.println("移动...");//父类自带方法
  4. }
  5. }
  6. class Cat extends Animal{
  7. public void catchMouse(){
  8. System.out.println("猫抓老鼠...");//子类特有方法
  9. }
  10. }
  11. class Bird extends Animal{
  12. public void fly(){
  13. System.out.println("鸟儿在飞...");//子类特有方法
  14. }
  15. }
  16. public class Test {
  17. public static void main(String[] args) {
  18. //使用List<Animal>后,List集合里只能保存Animal对象
  19. List<Animal> myList = new ArrayList<Animal>();
  20. myList.add(new Cat());
  21. myList.add(new Bird());
  22. //myList.add("A");//报错
  23. //myList.add(1);//报错
  24. //Iterator<Animal>表示迭代器迭代的是Animal类型
  25. Iterator<Animal> it = myList.iterator();
  26. while (it.hasNext()) {
  27. Animal obj = it.next();//迭代器返回的是Animal类型
  28. obj.move();//不需要强转,可以直接调用move方法
  29. }
  30. //遍历集合,取出Cat抓老鼠,Bird让它飞
  31. Iterator it1 = myList.iterator();
  32. while (it1.hasNext()){
  33. Object obj = it1.next();
  34. if (obj instanceof Cat){
  35. Cat c = (Cat) obj;
  36. c.catchMouse();
  37. }
  38. if (obj instanceof Bird){
  39. Bird b = (Bird) obj;
  40. b.fly();
  41. }
  42. }
  43. }
  44. }

3.泛型优缺点

3.1 优点

  • 集合中存储的元素更加统一。
  • 从集合中取出的元素类型是泛型指定的类型,不需要进行大量的向下转型。

    3.2 缺点

  • 导致集合中存储的元素缺乏多样性。大多数业务中,集合中元素类型比较统一,所以泛型特性比大家所认可。

    4.自动类型推断机制(钻石表达式、菱形语法)

    1. public class Test {
    2. public static void main(String[] args) {
    3. //Java7以前,使用带泛型的接口、类定义变量时,在构造器后面也必须带泛型
    4. List<String> strList = new ArrayList<String>();
    5. Map<String, Integer> scores = new HashMap<String, Integer>();
    6. //Java7以后,Java允许在构造器后不需要带完整的泛型信息,只给出<>即可
    7. List<String> strList1 = new ArrayList<>();
    8. Map<String, Integer> scores1 = new HashMap<>();
    9. }
    10. }

    三、深入泛型

    1.泛型接口、类

    1.1 泛型类、接口定义语法

    1. class/interface 类名/接口名<泛型标识,泛型标识,...>{
    2. private 泛型标识 变量名;
    3. public 泛型标识 方法名(泛型标识 形参){
    4. //方法体
    5. }
    6. }

    泛型标识只是个形参名,理论上可以随便自定义,常用E、T、K、V。

    1.2 泛型使用语法

    1. 类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
    2. //Java7之后可以进行自动类型推断,在new对象时具体数据类型可以省略
    3. 类名<具体的数据类型> 对象名 = new 类名<>();

    1.3 自定义泛型类、接口

    下面是Java5改写后的List接口、Iterator接口、Map的代码片段。

    1. //定义接口时指定了一个泛型形参,该形参名为E
    2. public interface List<E>{
    3. //在该接口里,E可作为类型使用
    4. void add(E x);
    5. Iterator<E> interator();
    6. }
    7. public interface Iterator<E>{
    8. E next();
    9. boolean hasNext();
    10. }
    11. //定义接口时指定了两个泛型形参,其形参名为K,V
    12. public interface Map<K, V>{
    13. //在该接口里,K,V可作为类型使用
    14. Set<K> keySet();
    15. V put(K key, V value);
    16. }

    泛型实质:允许在定义接口、类时声明泛型形参,泛型形参在整个接口、类体内可当成类型使用。

    1. public class Test<泛型形参随便写>{
    2. private 泛型形参随便写 x;
    3. void fun(泛型形参随便写 o){
    4. System.out.println(o);
    5. }
    6. public static void main(String[] args) {
    7. Test<String> str = new Test<>();
    8. str.fun("abc");
    9. }
    10. }

    1.4 泛型注意事项

  • 使用泛型类时,如果没有指定具体的数据类型,默认类型是Object。

  • 泛型的数据类型只能是类类型,不能是基本数据类型。
  • 不管为泛型形参传入哪一种数据类型实参,本质上都是同一个类,所以在静态方法、静态初始化块或者静态变量(它们都是类相关的)的声明和初始化中不允许使用泛型形参。

    1. List<int> list = new List<int>();//报错,不能是基本数据类型
  • 泛型类型在逻辑上可以看作是多个不同的类型,但本质上仍是同一类型。即同一泛型类,根据不同数据类型创建的对象,本质上还是同一类型—泛型类类型。

    1. public class Test<泛型形参随便写>{
    2. private 泛型形参随便写 x;
    3. void fun(泛型形参随便写 o){
    4. System.out.println(o);
    5. }
    6. public static void main(String[] args) {
    7. Test<String> a = new Test<>();
    8. Test<Integer> b = new Test<>();
    9. System.out.println(a.getClass());
    10. System.out.println(b.getClass());
    11. System.out.println(a.getClass() == b.getClass());
    12. }
    13. }
    14. //class com.sundegan.Test
    15. //class com.sundegan.Test
    16. //true

    1.5 泛型类派生子类

  • 子类也是泛型类,子类和父类的泛型标识要保持一致

    1. class ChildGeneric<T> extends Generic<T>
    2. class ChildGeneric<T,K,V> extends Generic<T>//子类进行泛型扩展,至少有一个泛型标识和父类一致
  • 子类不是泛型类,父类要明确泛型的数据类型

    1. class ChildGeneric extends Generic<String>

    1.6 泛型接口的使用

  • 实现类不是泛型类,接口要明确数据类型

  • 实现类也是泛型类,实现类和泛型的接口数据类型要一致

    1. public interface Test<T> {
    2. T getKey();
    3. }
    4. //1.实现类不是泛型类,泛型接口需明确数据类型,如不明确则默认为Object类型
    5. class A implements Test<String>{
    6. @Override
    7. public String getKey() {
    8. return "Hello";
    9. }
    10. }
    11. //2.实现类是泛型类,泛型标识需保持一致
    12. class B<T,E> implements Test<T>{
    13. private T key;
    14. private E value;
    15. @Override
    16. public T getKey() {
    17. return key;
    18. }
    19. public E getValue() {
    20. return value;
    21. }
    22. }

    2.泛型方法

    在定义类、接口时可以使用泛型形参,在该类的方法定义和成员变量定义、接口的方法定义中,这些泛型形参可以被当成普通类型来用。在定义类、接口时如果没有定义泛型形参,在定义方法时也可以自己定义泛型形参,这就是泛型方法。

    2.1 泛型方法定义

    修饰符 <T,E,K,V,... > 返回值类型 方法名(形参列表){
      方法体...
    }
    
  • 修饰符和返回值类型之间的非常重要,可以理解为声明此方法为泛型方法。

  • 只有声明了的方法才是泛型方法,泛型类中使用了泛型的成员方法并不是泛型方法。
  • 表明将使用泛型类型T,此时才可以在方法中使用泛型类型T。

    public class Test<E> {
      //泛型方法
      public <T> void getValue(ArrayList<T> list){
          for (int i = 0; i < list.size(); i++) {
              System.out.println(list.get(i));
          }
      }
      public static void main(String[] args) {
          Test<Integer> t = new Test();
          ArrayList<String> strList = new ArrayList<>();
          strList.add("a");
          strList.add("b");
          strList.add("c");
          t.getValue(strList);//泛型方法的调用,类型是在调用方法时来指定的。
      }
    }
    
    public class Test {
      //静态泛型方法,采用多个泛型类型
      public static <E,T,K> void printType(E e, T t, K k){
          System.out.println(e + "\t" + e.getClass().getSimpleName());
          System.out.println(t + "\t" + t.getClass().getSimpleName());
          System.out.println(k + "\t" + k.getClass().getSimpleName());
      }
      public static void main(String[] args) {
          Test.printType(100, "Java", true);//调用时确定具体类型,非常灵活
      }
    }
    

    2.2 可变参数泛型方法

    public <E> void print(E... e) {
        for (E e1 : e) {
            System.out.println(e1);
      }
    }
    
    public class Test {
      //可变参数泛型方法
      public static <E> void print(E... e) {
          for (E e1 : e) {
              System.out.println(e1);
          }
      }
      public static void main(String[] args) {
          Test.print(1,2,3);
          Test.print("a","b","c");
      }
    }
    

    2.3 注意事项

  • 泛型方法的具体数据类型是由调用该方法时确定的,和泛型类的数据类型无关系。

  • 泛型方法的泛型类型独立于泛型类,泛型方法的泛型标识和泛型类的泛型标识无关系,两者独立,因此,一个泛型方法可以操作多种数据类型,具有多样性,更加灵活。

    public class Test<E> {
      //泛型方法,即使和泛型类的泛型标识一样,但两者相互独立
      public <E> void getValue(ArrayList<E> list){
          for (int i = 0; i < list.size(); i++) {
              System.out.println(list.get(i));
          }
      }
       //泛型类的成员方法
      public void show(E e){
          System.out.println(e);
      }
      public static void main(String[] args) {
          Test<Integer> t = new Test();
          ArrayList<String> strList = new ArrayList<>();
          strList.add("a");
          strList.add("b");
          strList.add("c");
          t.getValue(strList);//泛型方法的调用,类型是由调用方法时来指定的。
      }
    }
    
  • 泛型类的成员方法,在其中使用的泛型数据类型和泛型类数据类型是一致的,由泛型类的数据类型确定。

    3.类型通配符

    类型通配符是指使用“?”代替具体的类型实参,所以,类型通配符是类型实参,而不是类型形参,可以代替任意类型。

    3.1 类型通配符的上限

    类/接口<? extends 实参类型>
    

    表示该类/接口的泛型类型只能是这个实参类型或该实参类型的子类类型。

    3.2 类型通配符的下限

    类/接口<? super 实参类型>
    

    表示该类/接口的泛型类型只能是这个实参类型或该实参类型的父类类型。

    3.3 设定泛型形参的上限

    Java不仅允许在使用通配符形参时设定上限,而且可以在定义泛型形参时设定上限,表示传给该泛型形参的实际类型要么是该上限类型,要么是该上限类型的子类类型。

    public class Test<T extends Number>{
      public static void main(String[] args){
          Test<Integer> i = new Test<>();
          Test<Double> d = new Test<>();
          //Test<String> s = new Test<>();//String不是Number的子类型,编译错误
    

    4.类型擦除与转换

    泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型有关的信息会被擦除,我们称之为类型擦除。
    image.png
    image.png
    image.png
    image.png

    5.泛型与数组

  • 可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象。

  • 可以通过java.lang.reflect.Array的newInstance(Class, int)创建T[ ]数组。

    public class Test {
      public static void main(String[] args) {
          //不能直接创建泛型数组对象
          //ArrayList<String>[] list = new ArrayList<String>[5];
          ArrayList[] list = new ArrayList[5];
          ArrayList<String>[] strList = list;
    
          ArrayList<Integer> intList = new ArrayList<>();
          intList.add(1);
          list[0] = intList;
          String s = strList[0].get(0);//ClassCastException异常
      }
    }
    //ClassCastException
    

    尽量不要使用泛型数组,使用泛型集合代替泛型数组。