泛型中? super T和? extends T的区别

本文深入解析Java泛型中的生产者和消费者原则,解释了<?extendsT>和<?superT>的区别与应用,通过实例展示了如何正确使用泛型通配符,并介绍了PECS原则。

经常发现有List<? super T>、Set<? extends T>的声明,是什么意思呢?<? super T>表示包括T在内的任何T的父类,<? extends T>表示包括T在内的任何T的子类

 

PECS

请记住PECS原则:生产者(Producer)使用extends,消费者(Consumer)使用super。

  • 生产者使用extends

如果你需要一个列表提供T类型的元素(即你想从列表中读取T类型的元素),你需要把这个列表声明成<? extends T>,比如List<? extends Integer>,因此你不能往该列表中添加任何元素。

  • 消费者使用super

如果需要一个列表使用T类型的元素(即你想把T类型的元素加入到列表中),你需要把这个列表声明成<? super T>,比如List<? super Integer>,因此你不能保证从中读取到的元素的类型。

  • 即是生产者,也是消费者

如果一个列表即要生产,又要消费,你不能使用泛型通配符声明列表,比如List<Integer>。

请参考java.util.Collections里的copy方法:(dest的元素类型 和src中的元素类型相同 或者是其父类)

/**
     * Copies all of the elements from one list into another.  After the
     * operation, the index of each copied element in the destination list
     * will be identical to its index in the source list.  The destination
     * list must be at least as long as the source list.  If it is longer, the
     * remaining elements in the destination list are unaffected. <p>
     *
     * This method runs in linear time.
     *
     * @param  <T> the class of the objects in the lists
     * @param  dest The destination list.
     * @param  src The source list.
     * @throws IndexOutOfBoundsException if the destination list is too small
     *         to contain the entire source List.
     * @throws UnsupportedOperationException if the destination list's
     *         list-iterator does not support the <tt>set</tt> operation.
     */
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        int srcSize = src.size();
        if (srcSize > dest.size())
            throw new IndexOutOfBoundsException("Source does not fit in dest");

        if (srcSize < COPY_THRESHOLD ||
            (src instanceof RandomAccess && dest instanceof RandomAccess)) {
            for (int i=0; i<srcSize; i++)
                dest.set(i, src.get(i));
        } else {
            ListIterator<? super T> di=dest.listIterator();
            ListIterator<? extends T> si=src.listIterator();
            for (int i=0; i<srcSize; i++) {
                di.next();
                di.set(si.next());
            }
        }
    }

泛型中使用通配符有两种形式:子类型限定<? extends xxx>和超类型限定<? super xxx>。

(1)子类型限定

下面的代码定义了一个Pair<T>类,以及Employee,Manager和President类。

    public class Pair<T> {  
        private T first;  
        private T second;  
      
        public Pair(T first, T second) {  
            this.first = first;  
            this.second = second;  
        }  
      
        public T getFirst() {  
            return first;  
        }  
      
        public T getSecond() {  
            return second;  
        }  
      
        public void setFirst(T newValue) {  
            first = newValue;  
        }  
      
        public void setSecond(T newValue) {  
            second = newValue;  
        }  
    }  
      
    class Employee {  
        private String name;  
        private double salary;  
          
        public Employee(String n, double s) {  
            name = n;  
            salary = s;  
        }  
          
        public String getName() {  
            return name;  
        }  
      
        public double getSalary() {  
            return salary;  
        }  
    }  
      
    class Manager extends Employee {  
        public Manager(String n, double s) {  
            super(n, s);  
        }  
    }  
    <pre name="code" class="java">  
    class President extends Manager {  
        public President(String n, double s) {  
            super(n, s);  
        }  
    }  

现在要定义一个函数可以打印Pair<Employee>

public static void printEmployeeBoddies(Pair<Employee> pair) {  
    System.out.println(pair.getFirst().getName() + ":" + pair.getSecond().getName());  
}  


 

可是有一个问题是这个函数输入参数只能传递类型Pair<Employee>,而不能传递Pair<Manager>和Pair<President>。例如下面的代码会产生编译错误

 

Manager mgr1 = new Manager("Jack", 10000.99);  
Manager mgr2 = new Manager("Tom", 10001.01);  
Pair<Manager> managerPair = new Pair<Manager>(mgr1, mgr2);  
PairAlg.printEmployeeBoddies(managerPair);

 

之所以会产生编译错误,是因为Pair<Employee>和Pair<Manager>实际上是两种类型。

由上图可以看出,类型Pair<Manager>是类型Pair<? extends Employee>的子类型,所以为了解决这个问题可以把函数定义改成
public static void printEmployeeBoddies(Pair<? extends Employee> pair)

但是使用通配符会不会导致通过Pair<? extends Employee>的引用破坏Pair<Manager>对象呢?例如:
Pair<? extends Employee> employeePair = managePair;employeePair.setFirst(new Employee("Tony", 100));
不用担心,编译器会产生一个编译错误。Pair<? extends Employee>参数替换后,我们得到如下代码
? extends Employee getFirst()
void setFirst(? extends Employee)
对于get方法,没问题,因为编译器知道可以把返回对象转换为一个Employee类型。但是对于set方法,编译器无法知道具体的类型,所以会拒绝这个调用。

(2)超类型限定

超类型限定和子类型限定相反,可以给方法提供参数,但是不能使用返回值。? super Manager这个类型限定为Manager的所有超类。

Pair<? super Manager>参数替换后,得到如下方法

? super Manager getFirst()
void setFirst(? super Manager)

编译器可以用Manager的超类型,例如Employee,Object来调用setFirst方法,但是无法调用getFirst,因为不能把Manager的超类引用转换为Manager引用。

超类型限定的存在是为了解决下面一类的问题。例如要写一个函数从Manager[]中找出工资最高和最低的两个,放在一个Pair中返回。

 

public static void minMaxSal(Manager[] mgrs, Pair<? super Manager> pair) {  
    if (mgrs == null || mgrs.length == 0) {  
        return;  
    }  
      
    pair.setFirst(mgrs[0]);  
    pair.setSecond(mgrs[0]);  
    //TODO  
}  

如此就可以这样调用

Pair<? super Manager> pair = new Pair<Employee>(null, null);  
minMaxSal(new Manager[] {mgr1, mgr2}, pair);  


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值