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

泛型中使用通配符有两种形式:子类型限定<? 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);

(3) <T extends Comparable<? super T>>

超类型限定还有一个使用形式: <T extends Comparable<? super T>>

比如要写一个函数返回数组中的最小对象。似乎可以如下定义。

public static <T> T min(T[] a)

但是要找到最小值必须进行比较,这样就需要T实现了Comparable接口。为此可以把方法定义改变成

public static <T extends Comparable> T min(T[] a)

因为Comparable接口本身也是泛型的,所以最终可以改写成

public static <T extends Comparable<T>> 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<Calendar> 而不是Comparable<GregorianCalendar>。

只要把min方法的申明改成<T extends Comparable<? super T>>就可以通过编译。

完整的方法如下。

注意,由于一个类可能没有实现Comparable方法,所以还需要定义另外一个方法,传入Comparator对象进行比较。

import java.util.Comparator;

public class ArrayAlg {
    public static <T extends Comparable<? super T>> 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> T min(T[] a, Comparator<? super T> 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<Employee>() {
            @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 <T extends Comparable<? super T>> void sort(List<T> list)
public static <T> void sort(List<T> list, Comparator<? super T> 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;
    }
}
时间: 2024-10-06 00:39:02

Java-泛型编程-使用通配符? extends 和 ? super的相关文章

如何理解 Java 中的 &lt;T extends Comparable&lt;? super T&gt;&gt;

1 <T extends Comparable<T>> 和 <T extends Comparable<? super T>> 有什么不同 <T extends Comparable<T>> 类型 T 必须实现 Comparable 接口,并且这个接口的类型是 T.只有这样,T 的实例之间才能相互比较大小.例如,在实际调用时若使用的具体类是 Dog,那么 Dog 必须 implements Comparable<Dog>

浅谈Java泛型中的extends和super关键字(转)

泛型是在Java 1.5中被加入了,这里不讨论泛型的细节问题,这个在Thinking in Java第四版中讲的非常清楚,这里要讲的是super和extends关键字,以及在使用这两个关键字的时候为什么会不同的限制.    首先,我们定义两个类,A和B,并且假设B继承自A.下面的代码中,定义了几个静态泛型方法,这几个例子随便写的,并不是特别完善,我们主要考量编译失败的问题: public class Generic{ //方法一 public static <T extends A> void

浅谈Java泛型中的extends和super关键字

泛型是在Java 1.5中被加入了,这里不讨论泛型的细节问题,这个在Thinking in Java第四版中讲的非常清楚,这里要讲的是super和extends关键字,以及在使用这两个关键字的时候为什么会不同的限制.  首先,我们定义两个类,A和B,并且假设B继承自A. package com.wms.test; import java.util.ArrayList; import java.util.List; public class Generic { public static void

Java泛型中的extends和super关键字

理解List<? extends T> list, T key, Comparator<? super T> c 这些一般用在方法形参类型上,用于接受泛型对象. 1.List<? extends T> 代表任意T的子类,比如List<? extends Person>表示可以接受任意一个泛型类型是任意Person子类的list对象,这个一般用于方法调用,表示接受的每一个List<? extends Person>中的元素都is a Person,

java泛型编程

一般的类和方法都是针对特定数据类型的,当写一个对多种数据类型都适用的类和方法时就需要使用泛型编程,java的泛型编程类似于C++中的模板,即一种参数化类型的编程方法,具体地说就是将和数据类型相关的信息抽象出来,主要提供通用的实现和逻辑,和数据类型相关的信息由使用时参数决定. 一.泛型类: 栈的实现 示例代码: package com.genericity; import org.junit.Test; /** * @Title: LinkedListStack.java * @Package c

JAVA泛型之&lt;? extends T&gt;:(通配符上限)和&lt;? super T&gt;(通配符下限)

一.通配符上限和通配符下限接受的类型 通配符上限:<? extends T> 通配符下限:<? super T> 以下代码是测试结果,注释为解释说明 1 package xayd.hjj; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 class A{} 7 class B extends A implements F{} 8 class C extends A{} 9 class D extends A{

Java学习之道:简述泛型通配符&lt;? extends T&gt;和&lt;? super T&gt;

<? extends T>和<? super T>含有JAVA5.0的新的概念.由于它们的外表导致了很多人误解了它们的用途: 1.<? extends T>首先你很容易误解它为继承于T的所有类的集合,这是大错特错的,相信能看下去你一定见过或用过List<? extends T>吧?为什么我说理解成一个集合是错呢?如果理解成一个集合那为什么不用List<T>来表示?所以<? extends T>不是一个集合,而是T的某一种子类的意思,记

Java泛型中extends和super的理解(转)

E – Element (在集合中使用,因为集合中存放的是元素) T – Type(Java 类) K – Key(键) V – Value(值) N – Number(数值类型) ? – 表示不确定的java类型(无限制通配符类型) S.U.V – 2nd.3rd.4th types Object – 是所有类的根类,任何类的对象都可以设置给该Object引用变量,使用的时候可能需要类型强制转换,但是用使用了泛型T.E等这些标识符后,在实际用之前类型就已经确定了,不需要再进行类型强制转换. ?

Java泛型的应用——T extends Comparable&lt;? super T&gt;

在观察Java源码的时候,发现了这么一个写法T extends Comparable<? super T>.不禁纳闷为什么要这么写呢?有什么好处吗,extends和super在这里的作用着实让人有点不清楚. 接下来,我将结合代码跟大家分享一下我关于这里泛型应用的看法. 1.  <T extends Comparable<? super T>>代表什么意思 大家可以明白的是这里应用到了Java的泛型,那么首先向大家说明一下这里extends的作用 extends后面跟的类