在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>.
- 若是操做与泛型类型无关,那么使用<?>