概述:

Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。本文综合多篇文章后,总结了Java 泛型的相关知识,希望可以提升你对Java中泛型的认知效率。
泛型出现的原因,就是为了解决类型转换的问题

引入泛型的意义:

  1. 适用于多种数据类型执行相同的代码(代码复用)
  2. 泛型中的类型在使用时指定,不需要强制转换(类型安全,编译器会检查类型)运行时异常-> 编译时异常

    泛型类:

    ```java class Point { private T var;

    public T getVar() { // 返回值的类型由外部决定

    1. return var;

    }

    public void setVar(T var) { // 设置的类型也由外部决定

     this.var = var;
    

    } }

public class Test { public static void main(String args[]) { Point p = new Point(); // 里面的var类型为String类型 p.setVar(“it”); // 设置字符串 System.out.println(p.getVar().length()); // 取得字符串的长度 } }


```java
public class Nodepad<K,V> {
    private K key;
    private V value;

    public K getKey() {
        return key;
    }

    public void setKey(K key) {
        this.key = key;
    }

    public V getValue() {
        return value;
    }

    public void setValue(V value) {
        this.value = value;
    }
}

public static void main(String[] args) {
    Nodepad<String,Integer>nodepad = null;

    nodepad = new Nodepad<>();

    nodepad.setKey("tom");
    nodepad.setValue(20);

    System.out.println("name:"+nodepad.getKey());
    System.out.println("age:" + nodepad.getValue());

}

泛型接口

public interface Info<E>{
    public E getVar();
}

public class InfoImpl<E> implements Info<E>{
    private E var;

    public InfoImpl(E var) {
        this.var = var;
    }

    public void setVar(E var) {
        this.var = var;
    }

    @Override
    public E getVar() {
        return this.var;
    }
}

泛型方法

为什么要使用泛型方法?
泛型类要在实例化的时候就指明类型,如果想换一种类型,就要重新new 一次,不够灵活,泛型方法可以在调用的时候就指明类型,更加灵活。

    public static  <T> T getObject(Class<T>tClass) throws InstantiationException, IllegalAccessException {
        T t = tClass.newInstance();
        return t;
    }

泛型的上下限

为什引入上下限?
为了解决泛型中隐含的转换问题,Java泛型加入了类型参数的上下边界机制。<? extends A>表示该类型参数可以是A(上边界)或者A的子类类型。编译时擦除到类型A,即用A类型代替类型参数。

<?> 无限制通配符
<? extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类
<? super E> super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类

// 使用原则《Effictive Java》
// 为了获得最大限度的灵活性,要在表示 生产者或者消费者 的输入参数上使用通配符,
// 使用的规则就是:生产者有上限、消费者有下限
1. 如果参数化类型表示一个 T 的生产者,使用 < ? extends T>;
2. 如果它表示一个 T 的消费者,就使用 < ? super T>;
3. 如果既是生产又是消费,那使用通配符就没什么意义了,因为你需要的是精确的参数类型。
private  <E extends Comparable<? super E>> E max(List<? extends E> e1) {
    if (e1 == null){
        return null;
    }
    //迭代器返回的元素属于 E 的某个子类型
    Iterator<? extends E> iterator = e1.iterator();
    E result = iterator.next();
    while (iterator.hasNext()){
        E next = iterator.next();
        if (next.compareTo(result) > 0){
            result = next;
        }
    }
    return result;
}

上述代码中的类型参数 E 的范围是>,可以分步查看:

  • 要进行比较,所以 E 需要是可比较的类,因此需要 extends Comparable<…>(注意这里不要和继承的 extends 搞混了,不一样)
  • Comparable< ? super E> 要对 E 进行比较,即 E 的消费者,所以需要用 super
  • 而参数 List< ? extends E> 表示要操作的数据是 E 的子类的列表,指定上限,这样容器才够大

    类型擦除:

    将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样

泛型的类型擦除原则:

  • 消除类型参数声明,即删除<>及其包围的部分。
  • 根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)。
  • 为了保证类型安全,必要时插入强制类型转换代码。
  • 自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”。

    无限制类型擦除

    擦除类定义中的类型参数

    当类定义中的类型参数没有任何限制时,在类型擦除中直接被替换为Object,即形如和<?>的类型参数都被替换为Object。
    image.png

    有限制类型擦除

    擦除类定义中的类型参数

    当类定义中的类型参数存在限制(上下界)时,在类型擦除中替换为类型参数的上界或者下界,比如形如和<? extends Number>的类型参数被替换为Number,<? super Number>被替换为Object。
    image.png

    擦除方法定义中的类型参数(原则同类定义类型擦除)

    image.png

    泛型的编译期类型检查

    ArrayList<String> list1 = new ArrayList(); //第一种 情况
    ArrayList list2 = new ArrayList<String>(); //第二种 情况
    
    new ArrayList()只是在内存中开辟了一个存储空间,可以存储任何类型对象,而真正涉及类型检查的是它的引用,因为我们是使用它引用list1来调用它的方法,比如说调用add方法,所以list1引用能完成泛型类型的检查。而引用list2没有使用泛型,所以不行。

类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象