一文带你搞懂Java泛型,带有限制的通配符泛型和泛型类型限界

414 阅读3分钟
  1. 泛型介绍

泛型提供了对代码重用的支持,如果除去对象的基本类型外,实现方法是相同,那么我们就可以用泛型实现。

  1. 数组类型的兼容性

  • 这里有一个基类Person
public class Person {
    private String name;
}
  • 两个Person的子类Employee和Student
public class Employee extends Person{
    
}
public class Student extends Person{

}

编译下面这段代码的时候不会出现任何问题,但是当运行的时候会发出java.lang.ArrayStoreException异常提示,因为拍p[0]实际上引用一个Employee,但是赋值的给了一个Student,产生了类型混乱。在Java中数组是类型兼容的,叫做协变数组类型。每个数组都明确他所允许存储的对象类型。如果将一个不兼容的类型插入到数组中,那么将抛出java.lang.ArrayStoreException异常。

Person[] p = new Employee[2];
p[0] = new Student();

image.png

  1. 简单的泛型类和接口

public class Demo<T> {
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}
  • 上述是一个简单的泛型类,类的声明包含一个类型参数,这个参数放在类的后面用一对尖括号括起来。
  • 在泛型类的内部,我们可以声明泛型类的域和使用泛型类型作为参数或返回类型的方法。
  • 用户可以创建像Demo < String >的,Demo < Integer >等类型。
  • 当用户创建Demo < String >这样的类时,如果此时setValue()方法的参数传一个Integer类型,那么将会产生一个编译错误。

对比第2节和第3节思考泛型带来的好处:协变数组类型在编译阶段允许将不符合类似的对象实例放入数组而不会产生编译错误,但是在运行时会触发java.lang.ArrayStoreException异常,而通过使类变成泛型,可以让只有在运行时才能报告的错误变成了在编译时的错误

  1. 菱形运算符

Java7以前我们创建泛型对象的时候用以下方法:

Demo<String> demo =new Demo<String>();

demo既然是Demo< String >类型,显然创建的对象肯定也是Demo< String >类型,任何其他类型的参数都会产生编译错误。 Java7新增加了菱形运算符,简化了代码,使上述语句可以写为

Demo<String> demo =new Demo<>();
  1. 带有限制的通配符

如下图static方法计算所有人的年龄总和,如果传递一个Collection< Person > 程序可以正常运行,可是如果传递一个Collection< Employee >会发生什么情况呢。

public static int getTotalAge(Collection<Person> p) {
    int total = 0;
    for (Person person : p) {
        if (person != null) {
            total += person.getAge();
        }
    }
    return total;
}

image.png

会提示类型不对,虽然Employee是Person的子类,所以泛型集合不是可变的,不能将Collection< Employee >传递到方法中,这样就产生了一个问题,泛型集合不是协变的,数组是协变的,若无附加语法,用户就会避免使用集合,因为失去协变性会使代码缺少灵活性。

为了弥补这个不足,Java5使用了通配符来表示参数类型的子类或超类。

public static int getTotalAge(Collection<? extends Person> p) {
    int total = 0;
    for (Person person : p) {
        if (person != null) {
            total += person.getAge();
        }
    }
    return total;
}

? extends Person 表示这个参数类型可以是Person的子类,或者? super Person表示这个参数类型可以是Person的超类。此时上诉方法就不会报错了

image.png

  1. 类型限界

参考下列方法,我们像编写一个查找最大值的问题,我们知道只有实现了Comparable的接口才能保证compaeTo方法的存在,所以下列代码调用不合法,编译错误。

image.png

此时我们可以使用类型限界解决这个问题,指定参数类型必须具有的性质,在这里我们要制定参数必须实现Comparable接口,这样才能调用compareTo方法。

image.png

  1. 泛型使用注意事项

  • 基本类型不能用作泛型参数
  • 在一个泛型类中,stati方法和static域均不可引用类的类型变量。
  • 不能创建一个泛型类型的实例
  • 不能创建一个泛型数组
  • 参数化类型的数组的实例化是非法的