泛型的本质就是参数化类型。也就是,将一个数据类型指定为参数。引入泛型有什么好处呢?

泛型可以将JDK 1.5之前在运行时才能发现的错误,提前到编译期。也就是说,泛型提供了编译时类型安全的检测机制。例如,一个变量本来是Integer类型,我们在代码中设置成了String,没有使用泛型的时候只有在代码运行到这了,才会报错。

而引入泛型之后就不会出现这个问题。这是因为通过泛型可以知道该参数的规定类型,然后在编译时,判断其类型是否符合规定类型。

1. 通配符

1.1. 类型通配符

在定义泛型类和泛型方法中使用的通配符

//定义泛型类
public class Generic<T> {
    T data;
    public Generic(T data) {
        setData(data);
    }
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
}

// 定义带返回值的泛型方法
private <T> T genericWithReturnMethod(T field) {
    System.out.println(field.getClass().toString());
    return field;
}

1.2. 无界通配符

无界通配符用?表示。看到这你可能会问,这不是跟T一样吗?为啥还要搞个?。他们主要区别在于,T主要用于声明一个泛型类或者方法,?主要用于使用泛型类和泛型方法。示例如下:

// 定义打印任何类型列表的函数
public static void printList(List<?> list) {
    for (Object elem: list) {
        System.out.print(elem + " ");
    }
}

// 调用上述函数
List<Integer> intList = Arrays.asList(1, 2, 3);
List<String> stringList = Arrays.asList("one", "two", "three");
printList(li);// 1 2 3 
printList(ls);// one two three

上述函数的目的是打印任何类型的列表。可以看到在函数内部,并没有关心List中的泛型到底是什么类型的,你可以将<?>理解为只提供了一个只读的功能,它去除了增加具体元素的能力,只保留与具体类型无关的功能。从上述的例子可以看出,它只关心元素的数量以及其是否为空,除此之外不关心任何事。

再反观T,上面我们也列举了如何定义泛型的方法以及如果调用泛型方法。泛型方法内部是要去关心具体类型的,而不仅仅是数量和不为空这么简单。

1.3. 上界通配符

使用<? extends T>表示,将未知的类型限制为特定类型或者该特定的类型的子类型。不能往里存,只能往外取,适合大量做获取操作的情景

public class ExtendTest {
    private static int countLength(List<? extends Animal> list) {
        return list.size();
    }

    public static void main(String[] args) {
        List<Pig> pigs = new ArrayList<>();
        List<Dog> dogs = new ArrayList<>();
        List<Cat> cats = new ArrayList<>();

        // 假装写入了数据
        int sum = 0;
        sum += countLength(pigs);
        sum += countLength(dogs);
        sum += countLength(cats);
        System.out.println(sum);
    }
}

class Animal {
}

class Pig extends Animal {
}

class Dog extends Animal {
}

class Cat extends Animal {
}

1.4. 下界通配符

使用<? super T>表示,将未知的类型限制为特定类型或者该特定的类型的超类型,也就是超类或者基类,适合大量做添加操作的情景

public class SuperTest {
    public static void main(String[] args) {
       Zoo<? super Pig> zoo=new Zoo<Animal>();
        zoo.add(new Pig());
    }
}
//动物园
class Zoo<T>{
    void add(T animal) {}
}
class Animal {}
class Pig extends Animal {}

2. 使用建议

2.1. 上界下界使用建议

  • extends 可用于返回类型限定,不能用于参数类型限定(换句话说:? extends xxx 只能用于方法返回类型限定,jdk能够确定此类的最小继承边界为xxx,只要是这个类的父类都能接收,但是传入参数无法确定具体类型,只能接受null的传入)。适合大量做获取操作的情景
  • super 可用于参数类型限定,不能用于返回类型限定(换句话说:? supper xxx 只能用于方法传参,因为jdk能够确定传入为xxx的子类,返回只能用Object类接收)适合大量做添加操作的情景