1 泛型概述

1.1 泛型的定义

泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,使代码可以应用于多种类型。

  1. public class ClassType<T>{
  2. private T a;
  3. public static void main(String[] args){
  4. ArrayList<T> arr;// T 称为类型参数变量,ArrayList<T> 称为泛型类型
  5. ArrayList<Integer> arr2;// Integer 称为实际类型参数,ArrayList<Integer>参数化的类型
  6. }
  7. }

1.2 泛型的作用

  • 简化代码(不用强制类型转换)
  • 健壮代码(编译时期没有警告,运行时就不会出现ClassCastExceptin异常)
  • 可读性和稳定性,在使用时就限定了类型。

    1.3 泛型的核心

    告诉编译器想使用的类型,然后编译器帮忙主力一切细节。

    1.4 泛型设计原则

    只要在编译时期没有出现警告,那么运行时期就不会出现**。**

    1.5 泛型的类型擦除机制

    1.5.1 类型擦除的概念

    1. List<String> l1 = new ArrayList<String>();
    2. List<Integer> l2 = new ArrayList<Integer>();
    3. System.out.println(l1.getClass() == l2.getClass());

    以上代码的输出结果是:True
    泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除****因此泛型类和普通类在JVM内部没有任何区别。

    1.5.2 类型擦除的局限性

    类型擦除,是泛型能够与之前的 java 版本代码兼容共存的原因。但也因为类型擦除,它会抹掉很多继承相关的特性,这是它带来的局限性。
    泛型类被类型擦除时。如果没有指定上限如<T>,则会被转译成普通的Object类型。如果指定了上限如<T extends String>,则类型参数被替换成上限类型。

    1.5.3 如何绕开类型擦除的局限性

    正常情况下,以下代码是无法通过编译的:

    1. public class Class1{
    2. public static void main(String[] args){
    3. List<Integer> ls = new ArrayList<>();
    4. ls.add(23);
    5. ls.add("Test");//这行是无法通过编译的。
    6. }
    7. }

    List中关于add的定义如下:

    1. public interface List<E> extends Collection<E>{
    2. boolean add(E e);
    3. }

    因为E代表任意类型,因此类型擦除时等同于**boolean add(Object obj)**
    需要通过反射机制绕过类型擦除限制:

    1. public class ToolTest {
    2. public static void main(String[] args) {
    3. List<Integer> ls = new ArrayList<>();
    4. ls.add(23);
    5. // ls.add("text");
    6. try {
    7. Method method = ls.getClass().getDeclaredMethod("add",Object.class);//这里设置了类型转换方法,将数据类型转换成Object类型,这是Java中对象的最基本类型,可以宽泛的理解为C++中的bit数据。
    8. method.invoke(ls, "test");//这里将"test"反射为object,类型擦除检查就可以绕过,成功正常存入。
    9. method.invoke(ls, 42.9f);
    10. } catch (NoSuchMethodException e) {
    11. // TODO Auto-generated catch block
    12. e.printStackTrace();
    13. } catch (SecurityException e) {
    14. // TODO Auto-generated catch block
    15. e.printStackTrace();
    16. } catch (IllegalAccessException e) {
    17. // TODO Auto-generated catch block
    18. e.printStackTrace();
    19. } catch (IllegalArgumentException e) {
    20. // TODO Auto-generated catch block
    21. e.printStackTrace();
    22. } catch (InvocationTargetException e) {
    23. // TODO Auto-generated catch block
    24. e.printStackTrace();
    25. }
    26. for ( Object o: ls){
    27. System.out.println(o);
    28. }
    29. }
    30. }

    ```java import java.lang.reflect.Method; import java.util.Arrays; import java.util.Objects;

public class Test { private T obj; public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } public static void main(String[] args) throws NoSuchMethodException { //创建对象并指定元素类型 Test tool = new Test<>(); tool.setObj(new String(“来福”)); String ss = Arrays.toString(tool.getObj().split(“”)) +”你好”; System.out.println(ss); String s = tool.getObj()+”你好”; System.out.println(s); //创建对象并指定元素类型 Test objectTool = new Test<>(); /**

  1. * 如果我在这个对象里传入的是String类型的,它在编译时期就通过不了了.
  2. */
  3. objectTool.setObj(10);
  4. int i = objectTool.getObj();
  5. System.out.println(i);
  6. try {
  7. Method method = objectTool.getClass().getDeclaredMethod("setObj", Object.class);
  8. method.invoke(objectTool, "拜拜");
  9. }catch (Exception e){
  10. e.printStackTrace();
  11. }
  12. System.out.println(objectTool.getObj());
  13. }

}

  1. 运行结果:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1750844/1612273040469-4d75ba87-0098-467b-b50e-348cab814c1b.png#align=left&display=inline&height=107&margin=%5Bobject%20Object%5D&name=image.png&originHeight=107&originWidth=326&size=2781&status=done&style=none&width=326)
  2. <a name="1JIhV"></a>
  3. ### 1.5.4 泛型的注意事项
  4. - 基本类型不能作为类型参数,使用包装类型
  5. - 同一个类不能实现泛型接口的两种变体。
  6. - 带泛型类型参数的转型或**instanceof**不会有任何效果。`b = (T)a;`
  7. - 不能重载泛型类型
  8. ```java
  9. public class ClassA<T, K> {
  10. void f(T t);
  11. void f(K k);
  12. }
  13. 这是不允许的。

  • 2 泛型的使用方式

    常用的List等持有对象类本身就是泛型类。

    2.1 泛型类

    泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来。
    用户使用时就直接将类型定义,不存在强制转换,运行时类型异常。

    1. public class ObjectTool<T> {
    2. private T obj;
    3. public T getObj() {
    4. return obj;
    5. }
    6. public void setObj(T obj) {
    7. this.obj = obj;
    8. }
    9. public static void main(String[] args) {
    10. //创建对象并指定元素类型
    11. ObjectTool<String> tool = new ObjectTool<>();
    12. tool.setObj(new String("钟福成"));
    13. String s = tool.getObj();
    14. System.out.println(s);
    15. //创建对象并指定元素类型
    16. ObjectTool<Integer> objectTool = new ObjectTool<>();
    17. /**
    18. * 如果我在这个对象里传入的是String类型的,它在编译时期就通过不了了.
    19. */
    20. objectTool.setObj(10);
    21. int i = objectTool.getObj();
    22. System.out.println(i);
    23. }
    24. }
    25. 这里面可以存在多个泛型
    26. public class ObjectTool<K, V, U, P>{};

    2.2 泛型接口

    与泛型类相似,只是将泛型定义在接口上。

    //定义一个泛型接口
    public interface Generator<T>{
      public T text();   
    }
    //实现这个接口
    public class NumGenerator implements Generator<Integer>{
      int[] a = {0, 181, 50};
      public Integer text{
          Random rand = new Random();
          return a[rand.nextInt(3)];
      }
      public static void main(String[] args){
          NumGenerator num = new NumGenerator();
          System.out.println(num.text());
      }
    }
    //再次实现这个接口
    public class StringGenerator implements Generator<String>{
      String[] a = {"hello", "hi", "nice to meet"};
      public String text{
          Random rand = new Random();
          return a[rand.nextInt(3)];
      }
      public static void main(String[] args){
          StringGenerator dialogue = new StringGenerator();
          System.out.println(dialogue.text());
      }
    }
    

    2.3 泛型方法

    仅仅在某一个方法上需要使用泛型….外界仅仅是关心该方法,不关心类其他的属性。
    泛型类与泛型方法无关,一个类可以是泛型类,而类中的方法也可以是泛型方法,两个没有联系。
    原则:如果可以使用泛型方法替代泛型类,那么尽可能使用泛型方法。这样可以使得泛型化更加具体、清楚。

    2.3.1 泛型方法的使用

    public class Class1{  
      //定义泛型方法.注意该样例与后续样例泛型方法的格式
      public <T> void show(T t) {
          System.out.println(t);
      }
      public static void main(String[] args) {
          //创建对象
          Class1 object1 = new Class1();
          //调用方法,传入的参数是什么类型,返回值就是什么类型
          object1.show("hello");
          object1.show(12);
          object1.show(12.5);
      }
    }
    

    2.3.2 泛型方法与可变参数

    public class Class1{  
      public static <T> List<T> makeList(T... args){
          List<T> result = new ArrayList<T>();
          for(T item : agrs){
              result.add(item);
          }
          return result;
      }
      public static void main(String[] args){
          List<String> ls = makeList("A");
          System.out.println(ls);
          ls = makeList("A", "B", "C");
          System.out.println(ls);
          ls = makeList("ABCDERFG".split("");
          System.out.println(ls);
      }
    }
    

    2.4 泛型数组

    不存在泛型数组,因为类型擦除之后无法识别该数组的类型。

    List<Integer>[] li2 = new ArrayList<Integer>[];
    List<Boolean> li3 = new ArrayList<Boolean>[];
    这两行代码都是无法编译通过的。
    

    3 泛型的上下边界

    泛型默认会进行类型擦除,如果不设置边界,会出现擦除为object类型的情况。

    3.1 上界通配符:extends


    3.2 下界通配符(超类型通配符):super

    声明的类型由某个特定类型的任何基类来界定

    4 泛型的通配符

    4.1 无限定通配符:?

    代表着类型未知,但是我们的确需要对于类型的描述再精确一点,我们希望在一个范围内确定类别,比如类型 A 及 类型 A 的子类都可以。

    public class Test2 <T,E extends T>{
      T value1;
      E value2;
    
      public <T> void test(T t,Collection<? extends T> collection){
      }
    }
    

    4.2 捕获转换

    public class ClassA{
      static <T> void f1(Holder<T> holder){
    
      }    
      static void f2(Holder<?> holder){
          f1(holder);//未指定类型的通配符捕获转换
      }   
    }