一、泛型的优点
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中。泛型是对Java的类型的一种扩展,以支持创建可以按类型进行参数化的类。
看个例子,如果没有泛型
// 这是可取的,因为我们的参数类型真的是String类型
List list = new ArrayList();
m.add("123");
String s = (String) m.get(0);
//但是有时候,我们并不知道里面是什么类型的数据,比如放了一个Integer
m.add(new Integer(1));
String s = (String) m.get(1);
// 这时候就会报错
通过引入泛型,我们就可以避免这种不必要的尴尬
List<Integer> list = new ArrayList<Integer>();
m.add("123"); // 编译通过
m.add(new Integer(1)); // 编译不通过
可见,这在我们的代码编译中增加了一层类型检查,这样可以防止将错误类型保存在集合中。
而且Java源码中的集合类,都已经使用了泛型。
二、定义泛型
我们可以在方法、接口、或者类上定义一个泛型,不过它们的范围是有区别的。如我们可以通过来定义一个泛型K。
类或接口上定义的泛型,影响范围是整个类或接口,而方法泛型只能影响这个方法,但是方法泛型优先级高于类泛型。下面代码会详细列出各种情况:
// 这里我们在类上定义两个泛型变量
public class GenericsClass<K, V> {
// 方法泛型与类泛型冲突,不加注解会有警告
// 这边的K是以传入的list的类型,而不是类中的K类型
@SuppressWarnings("hiding")
public <K> K testK(List<K> list) {
return list.get(0);
}
// 测试上面的问题,可以得出结论方法中的泛型优先级高于类,会将其覆盖
public static void main(String[] args) {
GenericsClass<String, Object> aClass = new GenericsClass<>();
List<Integer> list = new ArrayList<Integer>();
Integer aInteger =aClass.testK(list);
}
public <T> void testT(List<T> list) {
}
public void testV(List<V> list){
}
// T cannot be resolved to a type
// 编译不通过,方法泛型不能在其他方法上使用
public void testTT(List<T> list){
}
class InClass{
// 编译通过,内部类可以使用类上的泛型
public void testK(List<K> list){
}
}
}
class OutClass{
// K cannot be resolved to a type
// 编译不通过,类泛型只能在本类中使用
public void testK(List<K> list){
}
}
三、具体实例
1.ArrayList实例
如果我们想将ArrayList转换成一个数组,可能很多人会直接使用toArray();方法,但是这里会有一个问题,
List<String> list = new ArrayList<String>();
String[] stus = (String[]) list.toArray();// 运行时报错
// public Object[] toArray()
// 从源码可以发现,返回类型为Object[],这样会有类型转换问题
如果对结果又疑问,可以参考之前的博客:http://blog.csdn.net/odeviloo/article/details/52320030
这里可以用for循环来对Object[]数组中的对象逐个强制转型再放入stus中,代码如下
Object[] objs = list.toArray();
for(int i=0;i<objs.length;i++){
stus[i] = (Stirng) objs[i];
}
但是这很麻烦,好在我们有了泛型。可以看到ArrayList又提供了一个返回泛型的方法
// public <T> T[] toArray(T[] a)
String[] objsa = list.toArray(new String[6]);
一句话搞定,轻松方便
2.泛型静态工厂
老办法创建map,这样每次new的时候都需要制定类型
HashMap<String, Stu> oldmap = new HashMap<String, Stu>();
使用泛型静态工厂模式,会让代码看起来十分简单
public static <K, V> HashMap<K, V> newHashMap() {
return new HashMap<K, V>();
}
HashMap<String, Stu> map = FunctionI.newHashMap();
3.泛型中的继承
我们先定义类Stu继承于Stuc,并且有如下代码
public class FunctionI<E> {
public void onlyE(List<E> e) {
}
}
那么如果代码如下会有编译报错
FunctionI<Stu> fun = new FunctionI<Stu>();
List<Stuc> list = new ArrayList<Stuc>();
list.add(new Stuc());
fun.onlyE(list);
这是由于fun对象指定泛型E为Stu类型,而传入的list中的类型为其子类。
使用extends
我们增加如下方法
public void extendsE(List<? extends E> e) {
for (E obj : e) {
posh(obj);
}
}
那么再调用此方法,则可以编译通过。此方法可以传入任何继承于E类型的(这里也就是Stu)对象。如果我们还有Stub继承Stu,一样没有问题。
fun.extendsE(list);
注:?是一种实例化,是不确定类型的实例化,直接写?,等同于? extends Object。
使用super
同样,我们也可以使用super来放入任何是E父类的对象类型。
public void superE(List<? super E> e) {
while (!e.isEmpty())
e.remove(pop());
}
继承原则
这边有个原则PECS,表示producer-extends,consumer-super,如果没有遵守,我们可能还会碰到运行时的类型转换问题。
extendsE这里是e提供obj给FuncitonI对象的push(E e)使用,所以为生产者,传入push的必须为E的子类
superE是e通过FuncitonI使用其pop方法,也就是说传入的参数是消费者,pop返回的是FuncitonI的泛型E,那么List中的类型就要为E的父类,这样才能转换正确
4.多类型参数的泛型
public static <E> List<E> unionUndo(List<E> e1, List<E> e2) {
return new ArrayList<E>();
}
public static <E> List<E> unionExt(List<? extends E> e1, List<? extends E> e2) {
return new ArrayList<E>();
}
List<Integer> list1 = new ArrayList<Integer>();
List<Double> list2 = new ArrayList<Double>();
List<Number> list3 = FunctionI.unionUndo(list1, list2);
List<Number> list4 = FunctionI.<Number> unionExt(list1, list2);
unionUndo无法编译通过,因为泛型E无法确定,但是unionExt可以编译通过,因为我们在方法前指定了E的类型为Nubmer,而且入参都要为其子类,由于list1和2中存放的类型都是Number的子类所以可以编译通过。
5.解决针对自身的检查
查看Collections的源码可以看到一个极其负责的方法。
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;
}
我们就来看下这个方法的入参规则,其实,这里复杂的也就是前面部分
// GregorianCalendar extends Calendar
// Calendar implements Comparable<Calendar>
class Demo1<T extends Comparable<T>> {
}
Demo1<GregorianCalendar> p = null;
可见,Demo1编译不通过,这里相当于
class Demo2<T extends Comparable<? super T>> {
}
Demo2<GregorianCalendar> p2 = null;
Demo2则可以正常编译,这样就解决了Demo1中不满足的问题