Java泛型的使用


泛型

Java中的泛型定义

泛型是 JDK1.5 的一个新特性,泛型就是将类型参数化,其在编译时才确定具体的参数。 这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

好处

没有泛型的话,要容纳不同的类型,就用Object。但是这样就没有了类型检查。

List list = new ArrayList();
list.add("orzlinux.cn");
list.add(23);
String name = (String)list.get(0);
String number = (String)list.get(1);    //ClassCastException

根据《Java 编程思想》中的描述,泛型出现的动机在于:有许多原因促成了泛型的出现,而最引人注意的一个原因,就是为了创建容器类

好处如下:

  • 类型安全

    编译器就能检查出因Java类型不正确导致的异常。

  • 消除强制类型转换

    如ArrayList存放还是用Object数组,强制转换它内部做了。

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        transient Object[] elementData; 
    
        E elementData(int index) {
            return (E) elementData[index];
        }
        。。。
    }
    

    LinkedList就是直接Node结点了:

    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;
    }
    
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
    
  • 潜在的性能收益

    所有的代码都在编译器中完成,生成的代码跟不使用泛型时所写的代码几乎一致。

泛型原理

类型擦除:使用泛型的时候加上类型参数,编译器的编译的时候会去掉类型参数。

泛型只存在于编译阶段,而不存在于运行阶段。

例如:

package cn.orzlinux.skjava.base;

public class GenericsDemo<T> {
    private T num;
}

IDEA编译后反编译还会出现泛型:

package cn.orzlinux.skjava.base;

// 原因是在编译.java文件时虽然会擦除泛型,但是泛型信息会以
// 注释的形式记录下来,所有有的反编译工具可以正常展示泛型信息。
public class GenericsDemo<T> {
    private T num;

    public GenericsDemo() {
    }
}

严格来讲,应该是:

public class GenericsDemo {
    private Object num;

    public GenericsDemo() {
    }
}

大部分情况下,泛型类型都会以 Object 进行替换,而有一种情况则不是。那就是使用到了extendssuper语法的有界类型,如:

public class Caculate<T extends String> {
    private T num;
}

这种情况的泛型类型,num 会被替换为 String 而不再是 Object。

泛型中的通配符

限定通配符:一种是<? extends T>它通过确保类型必须是T或者T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T或者T的父类来设定类型的下界。

非限定通配符 :可以用任意类型来替代。如List<?> 的意思是这个集合是一个可以持有任意类型的集合,它可以是List<A>,也可以是List<B>,或者List<C>等等。

List<?> 则表示其中所包含的元素类型是不确定,其中可能包含的是 String,也可能是 Integer. 如果它包含了 String 的话,往里面添加 Integer 类型的元素就是错误的。作为对比,我们可以给一个 List<Object> 添加 String 元素,也可以添加 Integer 类型的元素, 因为它们都是 Object 的子类。

运行期间泛型是表现不出来的。

ArrayList<String> a = new ArrayList<>();
ArrayList<Integer> b = new ArrayList<>();
Class c1 = a.getClass();
Class c2 = b.getClass();
System.out.println(c1 == c2); // true

什么是PECS?

要使用的类:苹果和水果类。

class Fruit {}
class Apple extends Fruit {}

PECS指Producer Extends,Consumer Super。 如果你是想遍历,并对每一项元素操作时,此时这个集合是生产者(生产元素),应该使用 Collection<? extends Thing>。 如果你是想添加元素到collection中去,那么此时集合是消费者(消费元素)应该使用Collection<? super Thing>

image-20211118185520690

总结一下就是

LinkedList<? super Fruit>说明里面要存放的是Fruit或者它爹们,但是爹太多不确定,确定的是啥?Fruit自己及它的儿子们(可以转为Fruit变量)肯定是可以的。所以实际存只能存Fruit及其子孙。那要读呢?里面存的明面上还是Fruit或者它爹们,非要读的话只能赋给Object。

LinkedList<? extends Fruit>说明里面要存放的是Fruit或者它崽们,但是崽太多不确定,确定的是啥?啥都不确定。所以实际不能存(硬要说的话可以存null,因为null 可以表示任何类型。)。那要读呢?里面存的明面上还是Fruit或者它崽们,非要读的话可以直接赋给Fruit。

image-20211118195420879

问题来了

问题一:List<? extends Fruit>我都不能存我读个屁啊。

不能存但是初始化的时候可以给一个List,如下:

image-20211118200259686

LinkedList<? super Fruit>说是能放Fruit的爹们,但是也没见add也加不进去它爹啊。

同上:

image-20211118200520315

那晃悠这一圈干嘛呢?直接apples读不就完了?为的是让list能够接受不同的List<XXX>,但是又有一定的限制。

参考

java基础

Java 泛型原理

『Java』泛型中的PECS原则

Java 之泛型通配符 ? extends T 与 ? super T 解惑