Java泛型的一些事

 

在Java SE 1.5以前,沒有泛型的狀況的下,經過對類型Object的引用來實現參數的「任意化」「任意化」帶來的缺點是要作顯式的強制類型轉換,而這種轉換是要求開發者對實際參數類型能夠預知的狀況下進行的。對於強制類型轉換錯誤的狀況,編譯器可能不提示錯誤,在運行的時候纔出現異常,這是一個安全隱患。    java


泛型的好處在編譯的時候檢查類型安全,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,而且減小了出錯機會。 

本篇博文將從以下幾個方面入手,簡述一下Java泛型的一些事兒:安全

  • 泛型的修飾範圍
  • 使用&實現多重限制
  • 類型擦除
  • <? super T>, <? extends T>, <?>通配符的使用

 

泛型的修飾範圍

泛型能夠修飾接口,修改類,修飾方法。下面給出幾個例子:dom

泛型接口示例

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]<類型參數列表>返回值類型 方法名([形式參數列表]) 
其中,[]內的內容是可選的。 spa

下面舉幾個Collections工具類中的幾個泛型方法的例子: code

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字節代碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。這個過程就稱爲類型擦除。 dns

好比:接口

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.");
	}
}

這段代碼沒法編譯經過。 ci

在編譯後泛型類型是會被擦除的,在這個重載的例子中,由於參數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>.
  • 若是操做與泛型類型無關,那麼使用<?>
相關文章
相關標籤/搜索