泛型,你也可以自定义

一提到泛型,大家首先想到的就是。在集合中加入泛型,来规定集合中元素的类型。那么,什么是泛型?为什么要使用泛型?泛型除了集合以外,还可以在其他地方使用吗?我们今天就来了解一下。

1、为什么要使用泛型?

首先,我们来看一下ArrayList集合类的定义:

1
2
3
4
public class ArrayList<E> extends AbstractList<E>{
private transient Object[] elementData;
……
}

可以看到,ArrayList底层是使用数组实现的。而且,该数组是Object数组。这就意味着,在该数组中,我们可以存放任何类型的元素

1
2
3
4
List list = new ArrayList();
list.add("123");
list.add(new Integer(200));
list.add(new JFrame());

元素存放进集合后,我们遍历数组

1
2
3
4
5
for (int i = 0; i < list.size(); i++) {
Object obj = list.get(i);//1
String str = (String)obj;//2
System.out.println(name);//3
}

运行代码,在2处,会抛出java.lang.ClassCastException。这是类型转换异常。

原因是,在1处,我们先取出集合元素,创建了Object变量obj。按照多态的概念,父类变量可以指向任何一个子类对象。而Object是所有类的父类,所以变量obj可以指向任何一个子类对象。所以,1处的代码实际是:

1
2
3
Object obj = "123";
Object obj = new Integer(200);
Object obj = new JFrame();

很明显,在2处,将obj类型强转为String类型时,对于第一个元素是可以的。第一个元素指向的是字符串对象。但第二个元素是Integer对象,第三个元素是JFrame对象。将这样两个对象强转为String类型,当然会转换失败。

在以上代码中,存在的问题在于,ArrayList集合元素定义的是Object数组。该数组中可以存放任何类型的对象。所以,我们并不清楚集合元素,到底指向的是什么样的对象。所以,在使用集合元素时,会先进行类型转换,再调用方法。这时,如果不清楚集合中存放元素的类型,那么,就很容易抛出java.lang.ClassCastException类型转换异常。

那么能不能规定,集合中只能存放什么类型的元素,从而在使用集合元素时,开发者可以清晰的知道集合元素的类型,因此避免这样的类型转换异常呢?泛型,可以解决这些问题。

2、什么是泛型?

泛型,大家可以理解为“参数化类型”。也就是,可以将一个类中的某些属性的数据类型、方法参数类型、返回类型,都以变量方式表示。在使用/调用时传入具体的类型。

我们来看一下,集合中是如何规定集合元素类型的。

1
2
3
4
5
6
public class ArrayList<E>{
……
public E get(int index) {……}
public boolean add(E e) {……}
……
}

注意这个E。这可以看做是参数化的类型,ArrayList中采用泛型化定义之后,中的E表示类型形参,可以接收具体的类型实参,凡是出现E的地方,均表示相同的接受自外部的类型实参。换句话说,在定义ArrayList对象时,规定是什么类型,那么凡是引用类型变量E的地方,就是什么类型。

例如:

1
2
3
4
5
6
7
8
//定义了E参数的类型为String
ArrayList<String> list = new ArrayList<String>();
//添加元素时,add方法参数为E变量类型。由于定义list对象时,定义了类型为String,那么add方法形参变量类型就是String类型
list.add("abc");
//编译错误,定义list对象时,泛型为String,只能添加String对象
list.add(new JFrame());
//get方法返回的是E变量类型。由于定义list对象时,定义了类型为String,那么get方法返回的就是String类型
String str = list.get(0);

集合中解决类型转换异常的思路是:通过对象中泛型的定义,在添加元素时,规定只能添加某个类型的元素。如果添加其他类型的对象,那么编译错误。这样,取集合元素时,当然也只能取出同一类型的对象。这样就避免了类型转换异常。

所以,从在加上泛型定义的集合中取出元素时,无需进行类型转换,直接可以定义泛型变量接收集合元素。同样的两个ArrayList集合,也可以通过不同泛型的定义,从而规定存放集合的元素类型。

1
2
ArrayList<String> slist = new ArrayList<String>(); //只能存放String对象
ArrayList<Integer> ilist = new ArrayList<Integer>();//只能存放Integer对象

我们也可以给一个类或一个接口,定义多个泛型参数。

例如,Map集合定义如下:

1
2
3
4
public interface Map<K,V> {
V put(K key, V value);
V get(Object key);
}

定义Map对象时,我们可以给其中的键对象K,和值对象V,指明具体的类型。那么,凡是引用K类型的属性类型、方法参数,方法返回类型,就必须和定义Map对象时的K类型一致。同样,凡是引用V类型的属性类型、方法参数、方法返回类型,也必须和定义Map对象时的V类型一致。

1
2
3
4
5
6
7
8
9
//键定义为Integer类型,值定义为Student类型
Map<Integer,Student> map = new HashMap<Integer,Student);
//通过,1在添加元素时,会自动包装成Integer。键和值类型和定义map的类型匹配
map.put(1,new Student());
//编译错误,添加元素键对象的类型,和map对象中定义的键类型不匹配
map.put("1",new Student());
//返回类型和V值类型一致
Student s = map.get(1);

3、自定义泛型

那么,泛型的定义是否只限于集合框架呢?当然不是,凡是需要定义类型参数的类、接口、方法都可以使用泛型。

例如:集合框架中有一个工具类:Collections。该类中提供了一个sort排序方法,定义如下:

1
2
3
public class Collections{
public static void sort(List<T> list, Comparator<T> c){……}
}

sort方法在给集合元素排序时,需要由开发者定义排序的规则。这样,要由开发者实现Comparator接口,从而定义集合元素的比较规则。

不过,开发者在实现Comparator接口时,需要指出,是给什么样类型元素定义比较规则。

Comparator接口定义如下:

1
2
3
public interface Comparator<T> {
public int compare(T o1, T o2);
}

开发者在实现接口时,定义了Comparator比较器泛型类型后,compare方法参数的类型,就变成了定义比较器泛型的类型。

1
2
3
4
5
6
7
new Comparator<Student>(){
public int compare(Student o1, Student o2) {
return 0;
}
};

开发者在重写compare()方法时,就可以定义两个Student对象比较的规则了。

从以上内容,大家已经明白了泛型的具体运作过程。也知道了接口、类和方法也都可以使用泛型去定义。在具体使用时,可以分为泛型接口、泛型类和泛型方法。

在实际开发中,我们也可以通过泛型的定义,来规范一些属性、方法参数,以及返回类型。

例如,我们在做分页时,可以将页面显示的数据、总记录数和总页数,封装成专门用于分页的实体类。

1
2
3
4
5
public class CutPageBean{
private List list = new ArrayList();
private int count;
private int totalPage;
}

在需要分页的方法中,以CutPageBean做为返回类型。不过,在设计时,为了让开发者更清楚的知道,list属性中存放元素的类型,我们可以定义泛型去规范。

1
2
3
4
5
public class CutPageBean<T>{
private List<T> list = new ArrayList<T>();;
private int count;
private int totalPage;
}

这就意味着,创建CutPageBean时,规定了泛型类型后,list属性中存放元素的类型,就和定义CutPageBean对象时的类型一致。

1
2
CutPageBean<Student> cutBean = new CutPagBean<Student>();
cutBean.getList().add(new Student()); //在list集合属性中只能存放Student类型。

这样,在设计业务接口方法,以及实现时,可以明确的知道应该将什么样的对象,存放进分页实体对象定义的集合属性中。

总结:

1、泛型,表示参数化类型。可以将一个类中的某些属性的数据类型、方法参数类型、返回类型,都以变量方式表示。在使用/调用时传入具体的类型。
2、可以给一个类或一个接口,定义一个或多个泛型参数。使用的类型和泛型定义的类型一致。
3、泛型,我们也可以根据业务需要进行自定义。接口、类和方法也都可以使用泛型去定义。