通配符与上下界

类型通配符

泛型通配符一般是使用 ? 代替具体的类型实参,可以把 ? 看成所有类型的父类。当具体类型不确定的时候,可以使用泛型通配符 ?;当不需要使用类型的具体功能,只使用 Object 类中的功能时,可以使用泛型通配符 ?。类型通配符常用于对动态不定类型的处理,譬如方法接收一个集合参数,遍历集合并把集合元素打印出来:

public void test(List<Object> list){
    for(int i=0;i<list.size();i++){
        System.out.println(list.get(i));
    }
}

值得注意的是,该 test()方法只能遍历装载着 Object 的集合,泛型中的<Object> 并不是像以前那样有继承关系的,也就是说 List<Object>List<String> 是毫无关系的。Java 泛型提供了类型通配符 ?:

public void test(List<?> list){
    for(int i=0;i<list.size();i++){
        System.out.println(list.get(i));
    }
}

? 号通配符表示可以匹配任意类型,任意的 Java 类都可以匹配。当我们使用?号通配符的时候:就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。记住,只能调用与对象无关的方法,不能调用对象与类型有关的方法。因为直到外界使用才知道具体的类型是什么。也就是说,在上面的 List 集合,我是不能使用 add()方法的。因为 add()方法是把对象丢进集合中,而现在我是不知道对象的类型是什么。

泛型上下界

在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。泛型上下界的声明,必须与泛型的声明放在一起。

  • 上界通配符(extends):上界通配符为”extends”,可以接受其指定类型或其子类作为泛参。其还有一种特殊的形式,可以指定其不仅要是指定类型的子类,而且还要实现某些接口。例如:List<? extends A>表明这是 A 某个具体子类的 List,保存的对象必须是 A 或 A 的子类。对于 List<? extends A>列表,不能添加 A 或 A 的子类对象,只能获取 A 的对象。

  • 下界通配符(super):下界通配符为”super”,可以接受其指定类型或其父类作为泛参。例如:List<? super A>表明这是 A 某个具体父类的 List,保存的对象必须是 A 或 A 的超类。对于 List<? super A>列表,能够添加 A 或 A 的子类对象,但只能获取 Object 的对象。

PECS(Producer Extends Consumer Super)原则:

  • 作为生产者提供数据(往外读取)时,适合用上界通配符(extends);
  • 作为消费者消费数据(往里写入)时,适合用下界通配符(super)。

设定通配符上限

譬如 List 集合装载的元素只能是 Number 的子类或自身:

public static void main(String[] args) {
    //List集合装载的是Integer,可以调用该方法
    List<Integer> integer = new ArrayList<>();
    test(integer);

    //List集合装载的是String,在编译时期就报错了
    List<String> strings = new ArrayList<>();
    test(strings);
}

public static void test(List<? extends Number> list) {
}

/** 数字支撑类 */
@Getter
@Setter
@ToString
public class NumberHolder<T extends Number> {
    /** 通用取值 */
    private T value;

    /** 构造函数 */
    public NumberHolder() {}

    /** 构造函数 */
    public NumberHolder(T value) {
        this.value = value;
    }
}

/** 打印取值函数 */
public static <T extends Number> void printValue(GenericHolder<T> holder) {
    System.out.println(holder.getValue());
}

设定通配符下限

//传递进来的只能是Type或Type的父类
<? super Type>

public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}

当我们想要创建一个 TreeSet<String> 类型的变量的时候,并传入一个可以比较 String 大小的 Comparator。那么这个 Comparator 的选择就有很多了,它可以是Comparator<String>,还可以是类型参数是 String 的父类,比如说Comparator<Objcet>。无论是设定通配符上限还是下限,都是不能操作与对象有关的方法,只要涉及到了通配符,它的类型都是不确定的。

通配符和泛型方法

大多时候,我们都可以使用泛型方法来代替通配符的:

//使用通配符
public static void test(List<?> list) {}

//使用泛型方法
public <T> void  test2(List<T> t) {}

如果参数之间的类型有依赖关系,或者返回值是与参数之间有依赖关系的。那么就使用泛型方法。如果没有依赖关系的,就使用通配符,通配符会灵活一些。

上一页