Java泛型通配符 使用的场景(转)

Java泛型支持通配符,可以单独使用 '?' 来表示任意类型,也可以使用extends关键字表示某一个类或接口的子类,也可以使用super关键字表示某一个类,接口的父类型。

本文对读写操作的分析得出什么时候该使用extends 和 super。

<? super T>使“读”受到限制

先来看一个例子:

比如要实例化一个List<? super Integer>的numberList ,我们可以使用Integer, Number和Object来完成,如:

Java代码 复制代码 收藏代码
  1. List<? super Integer> numberList = new ArrayList<Integer>();
  2. List<? super Integer> numberList = new ArrayList<Number>();
  3. List<? super Integer> numberList = new ArrayList<Object>();
List<? super Integer> numberList = new ArrayList<Integer>();  
List<? super Integer> numberList = new ArrayList<Number>();   
List<? super Integer> numberList = new ArrayList<Object>();  



从这个例子中可以看出,numberList可能是指向List<Integer>, 可能指向List<Number>, 也可能指向List<Object>, 这样多可能性将会限制“读”操作。

因为,

  • 我们并不能保证读到的是Integer,因为numberList可能指向List<Number>或者List<Object>。
  • 我们并不能保证读到的是Number,因为numberList可能指向List<Object>。
  • 唯一能保证的的就是我们将得到一个Object或者是Object的子类的一个实例,但是我们并不知道具体的子类是什么。



如下,有如下这样的一个get方法,使用了List<? super T>:

Java代码 复制代码 收藏代码
  1. privatestatic <T> T get(List<? super T> list, int index) {
  2. }
 private static <T> T get(List<? super T> list, int index) {
    
 }



那么,我们怎么知道list中存放的内容是什么类型呢? 我们只能知道是T或者T的父类,仅此而已。但T的父类具体是什么,不得而知了。

“读”操作不能使用<? super T>, 而应该使用<? extends T>, 让我们来看看Collections中的一些方法吧。

Java代码 复制代码 收藏代码
  1. /**
  2. * Gets the ith element from the given list by repositioning the specified
  3. * list listIterator.
  4. */
  5. privatestatic <T> T get(ListIterator<? extends T> i, int index) {
  6. T obj = null;
  7. int pos = i.nextIndex();
  8. if (pos <= index) {
  9. do {
  10. obj = i.next();
  11. } while (pos++ < index);
  12. } else {
  13. do {
  14. obj = i.previous();
  15. } while (--pos > index);
  16. }
  17. return obj;
  18. }
    /**
     * Gets the ith element from the given list by repositioning the specified
     * list listIterator.
     */
    private static <T> T get(ListIterator<? extends T> i, int index) {
        T obj = null;
        int pos = i.nextIndex();
        if (pos <= index) {
            do {
                obj = i.next();
            } while (pos++ < index);
        } else {
            do {
                obj = i.previous();
            } while (--pos > index);
        }
        return obj;
    }



Java代码 复制代码 收藏代码
  1. publicstatic <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c) {
  2. if (c==null)
  3. return binarySearch((List) list, key);
  4. if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
  5. return Collections.indexedBinarySearch(list, key, c);
  6. else
  7. return Collections.iteratorBinarySearch(list, key, c);
  8. }
    public static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c) {
        if (c==null)
            return binarySearch((List) list, key);

        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
            return Collections.indexedBinarySearch(list, key, c);
        else
            return Collections.iteratorBinarySearch(list, key, c);
    }




<? extedns T>使“写”受到限制

一个List<? extends Number>的numberList可能指向List<Number>, 可能指向List<Integer>, 也可能指向List<Object>.

Java代码 复制代码 收藏代码
  1. List<? extends Number> numberList = new ArrayList<Number>();
  2. List<? extends Number> numberList = new ArrayList<Integer>();
  3. List<? extends Number> numberList = new ArrayList<Double>();
List<? extends Number> numberList = new ArrayList<Number>();
List<? extends Number> numberList = new ArrayList<Integer>();
List<? extends Number> numberList = new ArrayList<Double>();



这种多可能性将让<? extends T> 的“写”操作受到限制。

因为,

  • 我们不能添加一个Integer类型的值,因为numberList可能指向List<Double>
  • 我们不能添加一个Double类型的值,因为numberList可能指向的是List<Integer>
  • 我们不能添加一个Number类型的值,因为numberList可能指向的是List<Integer>



我们不能添加任何对象到List<? extends T>, 那是因为我们并不能保证实际指向的是什么类型的List,所以也就不能保证想要添加的对象是List所允许的类型。

唯一能保证的是只能读取并得到一个T或者是T的子类。



上面的分析,我们可以得出一个结论, 那就是<? extends T> 不适合“写”操作,<? super T> 不适合“读”操作。

其实,
Collections中的copy方法很好的使用<? extends T> 和 <? super T>的经典案例。

另外还有一个PECS原则供参考:

PECS原则-->




在 Collections#copy方法中,src (the producing list)使用extends, 而 desc (the consuming list) 使用super. 代码如下:

Java代码 复制代码 收藏代码
  1. publicstatic <T> void copy(List<? super T> dest, List<? extends T> src) {
  2. int srcSize = src.size();
  3. if (srcSize > dest.size())
  4. thrownew IndexOutOfBoundsException("Source does not fit in dest");
  5. if (srcSize < COPY_THRESHOLD ||
  6. (src instanceof RandomAccess && dest instanceof RandomAccess)) {
  7. for (int i=0; i<srcSize; i++)
  8. dest.set(i, src.get(i));
  9. } else {
  10. ListIterator<? super T> di=dest.listIterator();
  11. ListIterator<? extends T> si=src.listIterator();
  12. for (int i=0; i<srcSize; i++) {
  13. di.next();
  14. di.set(si.next());
  15. }
  16. }
  17. }
    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());
            }
        }
    }



通配符<?>的使用

List<?>表示的是任意类型。因为编译器不知道List中容纳的是什么类型的元素,所以不能对其进行增加,修改的操作。 但是,List<?>拥有删除的功能,因为这些功能与泛型类型没有关系。

所以,List<?>适合用于与泛型类型无关的方法,比如remove, shuffle等。

我们来看看Collections中的几个方法吧:

Java代码 复制代码 收藏代码
  1. publicstaticvoid shuffle(List<?> list, Random rnd) {
  2. int size = list.size();
  3. if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) {
  4. for (int i=size; i>1; i--)
  5. swap(list, i-1, rnd.nextInt(i));
  6. } else {
  7. Object arr[] = list.toArray();
  8. // Shuffle array
  9. for (int i=size; i>1; i--)
  10. swap(arr, i-1, rnd.nextInt(i));
  11. // Dump array back into list
  12. ListIterator it = list.listIterator();
  13. for (int i=0; i<arr.length; i++) {
  14. it.next();
  15. it.set(arr[i]);
  16. }
  17. }
  18. }
public static void shuffle(List<?> list, Random rnd) {
        int size = list.size();
        if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) {
            for (int i=size; i>1; i--)
                swap(list, i-1, rnd.nextInt(i));
        } else {
            Object arr[] = list.toArray();

            // Shuffle array
            for (int i=size; i>1; i--)
                swap(arr, i-1, rnd.nextInt(i));

            // Dump array back into list
            ListIterator it = list.listIterator();
            for (int i=0; i<arr.length; i++) {
                it.next();
                it.set(arr[i]);
            }
        }
    }




Java代码 复制代码 收藏代码
  1. publicstaticvoid swap(List<?> list, int i, int j) {
  2. final List l = list;
  3. l.set(i, l.set(j, l.get(i)));
  4. }
    public static void swap(List<?> list, int i, int j) {
        final List l = list;
        l.set(i, l.set(j, l.get(i)));
    }



Java代码 复制代码 收藏代码
  1. publicstaticvoid rotate(List<?> list, int distance) {
  2. if (list instanceof RandomAccess || list.size() < ROTATE_THRESHOLD)
  3. rotate1(list, distance);
  4. else
  5. rotate2(list, distance);
  6. }
    public static void rotate(List<?> list, int distance) {
        if (list instanceof RandomAccess || list.size() < ROTATE_THRESHOLD)
            rotate1(list, distance);
        else
            rotate2(list, distance);
    }



总结:

    • 只用于“读”功能时,泛型结构使用<? extends T>

  • 只用于“写”功能时,泛型结构使用<? super T>
  • 如果既用于“写”,又用于“读”操作,那么直接使用<T>.
  • 如果操作与泛型类型无关,那么使用<?>

猜你喜欢

转载自frank1998819.iteye.com/blog/1940743