在Java SE 1.5以前,沒有泛型的狀況的下,經過對類型Object的引用來實現參數的「任意化」,「任意化」帶來的缺點是要作顯式的強制類型轉換,而這種轉換是要求開發者對實際參數類型能夠預知的狀況下進行的。對於強制類型轉換錯誤的狀況,編譯器可能不提示錯誤,在運行的時候纔出現異常,這是一個安全隱患。 java
泛型的好處是在編譯的時候檢查類型安全,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,而且減小了出錯機會。
本篇博文將從以下幾個方面入手,簡述一下Java泛型的一些事兒:安全
泛型能夠修飾接口,修改類,修飾方法。下面給出幾個例子: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>
Java泛型支持通配符, 能夠單獨使用 '?' 來表示任意類型, 也可使用extends關鍵字表示某一個類或接口的子類, 也可使用super關鍵字表示某一個類,接口的父類型。
接下來,咱們給出一些例子,並小結一下何時該使用extends 和 super。
先來看一個例子:
好比,要實例化一個List<? super Integer>的numberList ,咱們可使用Integer, Number和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>:
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; }
一個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>.
- 若是操做與泛型類型無關,那麼使用<?>