在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, 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;
}
<? 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>.
- 如果操作与泛型类型无关,那么使用<?>
来源:oschina
链接:https://my.oschina.net/u/2911530/blog/772295