Java-泛型编程-使用通配符? extends 和 ? super

泛型中使用通配符有两种形式:子类型限定和超类型限定

(1)子类型限定

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

public class Pair {
    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);
    }
}
class President extends Manager {
    public President(String n, double s) {
        super(n, s);
    }
}

 
  
 现在要定义一个函数可以打印Pair 
  
    public static void printEmployeeBoddies(Pair pair) {
        System.out.println(pair.getFirst().getName() + ":" + pair.getSecond().getName());
    }
可是有一个问题是这个函数输入参数只能传递类型Pair,而不能传递Pair和Pair。例如下面的代码会产生编译错误
        Manager mgr1 = new Manager("Jack", 10000.99);
        Manager mgr2 = new Manager("Tom", 10001.01);
        Pair managerPair = new Pair(mgr1, mgr2);
        PairAlg.printEmployeeBoddies(managerPair);

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

Java-泛型编程-使用通配符? extends 和 ? super_第1张图片

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

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

(2)超类型限定

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

Java-泛型编程-使用通配符? extends 和 ? super_第2张图片

Pair参数替换后,得到如下方法

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

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

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

    public static void minMaxSal(Manager[] mgrs, Pair pair) {
        if (mgrs == null || mgrs.length == 0) {
            return;
        }
        
        pair.setFirst(mgrs[0]);
        pair.setSecond(mgrs[0]);
        //TODO
    }
如此就可以这样调用
        Pair pair = new Pair(null, null);
        minMaxSal(new Manager[] {mgr1, mgr2}, pair);

(3) >

超类型限定还有一个使用形式: >

比如要写一个函数返回数组中的最小对象。似乎可以如下定义。
public static T min(T[] a)
但是要找到最小值必须进行比较,这样就需要T实现了Comparable接口。为此可以把方法定义改变成
public static T min(T[] a)
因为Comparable接口本身也是泛型的,所以最终可以改写成
public static > T min(T[] a)

但是考虑到如下的使用场景

        GregorianCalendar[] birthdays = {
                new GregorianCalendar(1906, Calendar.DECEMBER, 9),
                new GregorianCalendar(1815, Calendar.DECEMBER, 10),
                new GregorianCalendar(1903, Calendar.DECEMBER, 3),
                new GregorianCalendar(1910, Calendar.JUNE, 22),
        };
        
        System.out.println("Min Age = " + ArrayAlg.min(birthdays).getTime());
java.util.GregorianCalendar继承自java.util.Calendar。GregorianCalendar本身没有实现Comparable接口,其父类Calendar实现了Comparable接口。
但是编译器会对ArrayAlg.min(birthdays)报错,因为GregorianCalendar实现了Comparable 而不是Comparable
只要把min方法的申明改成>就可以通过编译。
完整的方法如下。
注意,由于一个类可能没有实现Comparable方法,所以还需要定义另外一个方法,传入Comparator对象进行比较。
import java.util.Comparator;

public class ArrayAlg {
    public static > T min(T[] a) {
        if (a == null || a.length == 0) {
            return null;
        }
        T smallest = a[0];
        for (int i = 1; i < a.length; i++) {
            if (smallest.compareTo(a[i]) > 0) {
                smallest = a[i];
            }
        }
        return smallest;
    }
            
    public static  T min(T[] a, Comparator c) {
        if (a == null || a.length == 0) {
            return null;
        }
        T smallest = a[0];
        for (int i = 1; i < a.length; i++) {
            if (c.compare(smallest, a[i]) > 0) {
                smallest = a[i];
            }
        }
        return smallest;
    }
}

传入Comparator作为参数的min方法的调用例子。
        Manager[] managers = new Manager[] {
                new Manager("Jack", 10000.99),
                new Manager("Tom", 10001.01),
        };
        
        Manager minSalMgr = ArrayAlg.min(managers, new Comparator() {
            @Override
            public int compare(Employee e1, Employee e2) {
                return Double.compare(e1.getSalary(), e2.getSalary());
            }            
        });
        System.out.println("Min. Salary = " + minSalMgr.getName());

在Java的类库中,大量使用了这种类型的参数。例如在java.util.Collections类中就定义了下面两个方法
public static > void sort(List list)
public static  void sort(List list, Comparator c) 

(4)无限定通配符?

无限定通配符表示不需要限定任何类型。例如Pair
参数替换后的Pair类有如下方法
? getFirst()
void setFirst(?)
所以可以调用getFirst方法,因为编译器可以把返回值转换为Object。
但是不能调用setFirst方法,因为编译器无法确定参数类型,这就是Pair和Pair方法的根本不同。
无限定通配符的出现是为了支持如下的函数定义。
public class PairAlg {    
    public static boolean hasNulls(Pair p) {
        return p.getFirst() == null || p.getSecond() == null;
    }
}

你可能感兴趣的:(Java)