Java泛型的一些事

扶醉桌前 提交于 2020-05-08 23:06:46

 

在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。    


泛型的好处在编译的时候检查类型安全,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。 

本篇博文将从如下几个方面入手,简述一下Java泛型的一些事儿:

  • 泛型的修饰范围
  • 使用&实现多重限制
  • 类型擦除
  • <? super T>, <? extends T>, <?>通配符的使用

 

泛型的修饰范围

泛型可以修饰接口,修改类,修饰方法。下面给出几个例子:

泛型接口示例

public interface List<E> extends Collection<E> {
 ...

 Iterator<E> iterator();

 boolean add(E e);

 boolean addAll(Collection<? extends E> c);

 ...
}

泛型类示例

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    ...
    public Iterator<E> iterator() {
        return new Itr();
    }

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    ... 

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
    
    ...
}

泛型方法

泛型方法语法:

[访问权限修饰符][static][final]<类型参数列表>返回值类型 方法名([形式参数列表]) 
其中,[]内的内容是可选的。 

下面举几个Collections工具类中的几个泛型方法的例子: 

    public static <T> void sort(List<T> list, Comparator<? super T> c) {
	Object[] a = list.toArray();
	Arrays.sort(a, (Comparator)c);
	ListIterator i = list.listIterator();
	for (int j=0; j<a.length; j++) {
	    i.next();
	    i.set(a[j]);
	}
    }

 

    public static <T extends Comparable<? super T>> void sort(List<T> list) {
	Object[] a = list.toArray();
	Arrays.sort(a);
	ListIterator<T> i = list.listIterator();
	for (int j=0; j<a.length; j++) {
	    i.next();
	    i.set((T)a[j]);
	}
    }

 

    public static <T> T min(Collection<? extends T> coll, Comparator<? super T> comp) {
        if (comp==null)
            return (T)min((Collection<SelfComparable>) (Collection) coll);

	Iterator<? extends T> i = coll.iterator();
	T candidate = i.next();

        while(i.hasNext()) {
	    T next = i.next();
	    if (comp.compare(next, candidate) < 0)
		candidate = next;
	}
	return candidate;
    }

使用&实现多重限制

如果一个类型有多个限制条件,可以使用&实现多重限制。

    public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll) {
        Iterator<? extends T> i = coll.iterator();
        T candidate = i.next();

        while (i.hasNext()) {
            T next = i.next();
            if (next.compareTo(candidate) < 0)
                candidate = next;
        }
        return candidate;
    }

 

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {
        Iterator<? extends T> i = coll.iterator();
        T candidate = i.next();

        while (i.hasNext()) {
            T next = i.next();
            if (next.compareTo(candidate) > 0)
                candidate = next;
        }
        return candidate;
    }

 

类型擦除

类型擦除(type erasure)。 Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。 

比如:

import java.util.List;

public class TypeErasureTest {

	public void test(List<String> ls) {
		System.out
				.println("Mthod test(List<String> ls) is calling.");

	}

	public void test(List<Integer> ls) {
		System.out.println("Mthod test(List<Integer> ls) is calling.");
	}
}

这段代码无法编译通过。 

在编译后泛型类型是会被擦除的,在这个重载的例子中,因为参数List<Integer>和 List<String>编译之后都被擦除了,变成了一样的原生类型List<E>,擦除动作导致这两个方法的特征签名一样,这样两个相同的方法将不能满足重载,最后导致编译失败。 


在编译后所有的泛型类型都会做相应的转化: 

  • List<String>, List<Integer>, List<T>擦除后的类型为List
  • List<String>[] 擦除后的类型为List[]
  • List<? extends E>, List<? super E> 擦除后的类型为List<E>
  • List<T extends Serialiable & Clonable> 擦除后为List<Serialiable>

<? super T> , <? extends T> , <?> 通配符的使用

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

接下来,我们给出一些例子,并小结一下什么时候该使用extends 和 super。 

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

先来看一个例子: 

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

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>: 

 private static <T> T get(List<? super T> list, int index) {
    
 }

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


“读”操作不能使用<? super T>, 而应该使用<? extends T>, 

让我们来看看java.util.Collections类中的一些方法吧。 

    /**
     * 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;
    }

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

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

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. 代码如下: 

    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中的几个方法吧: 

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]);
            }
        }
    }

 

    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>.
  • 如果操作与泛型类型无关,那么使用<?>
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!