所谓泛型就是“参数化类型”,是JDK1.5后引入的。在java的集合当中,常用的List,Map都用到了泛型,可以指定集合中放置的元素类型,不指定默认为Object类型。使用泛型的优点主要有类型安全和取消强制类型转换。下面从一小段代码来看一下泛型的使用:
public class GenericMain {
public static void main(String[] args) {
List list1 = new ArrayList();
list1.add("apple");
list1.add(123);
for(Object obj : list1) {
String value = (String) obj; //1:java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
System.out.println(value);
}
List<String> list2 = new ArrayList<String>();
list2.add("hello");
//list2.add(123); //2
list2.add("world");
for(Object obj : list2) {
String value = obj;
System.out.println(value);
}
}
}
上面的代码中,list1实例在初始化时,未指定元素类型,默认为Object,所以在加入元素的时候,既可以加入字符串类型,也可以加入整型。同样的,取出元素的时候也是Object类型,一般操作会进行类型转化,容易出现1中的ClassCastException异常。所以一般使用集合时,会指定集合中元素的类型,类如list2,初始化时指定了集合类型为String,这样在加入非String类型元素的时候会直接编译报错,例如2中编译报错。除此之外,在取出的时候默认取出的类型为String,这样就不会有类型转化错误,所以使用泛型也有一定的潜在性能优化。
可以看一下ArrayList的部分源码:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // non-private to simplify nested class access
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//其他先省略
}
从上面AbstractList的源码可以看出来,List,ArrayList都使用了泛型,定义了参数化类型<E>,在使用时指定具体类型,例如String后。在编译的时候,这个List里面的E都会认为是String,在get和set的时候也是这样,所以不能放入非String类型的数据。
下面进入正文,泛型主要用于泛型接口,泛型类,泛型方法。
一:泛型接口
定义泛型接口,和普通泛型接口相比,在接口名后面用<泛型类型变量>,当需要多个参数时,用逗号隔开。上面的List<E>就是一个典型的泛型接口。
public interface DataHandlerI<E, T> {
void setData(E data);
E getData();
}
这里的E是随便定义的,常用的有
- E elemet 元素,一般集合会用E;
- K key 键;
- V value 指,K,V常用与键值对的集合,例如Map;
- T 一般用于各种类型,多个泛型类型的时候,一般后面加数字,例如T1, T2;
接口DataHandlerI定义了参数化类型E,T,但是实现这个接口的类不一定是泛型类。例如下面的AbstractDataHandler和StringDataHandler。
public class AbstractDataHandler<E> implements DataHandlerI<E> {
private E data;
public void setData(E data) {
this.data = data;
}
public E getData() {
return this.data;
}
}
public class StringDataHandler implements DataHandlerI<String>{
String data;
public void setData(String data) {
this.data = data;
}
public String getData() {
return this.data;
}
}
二:泛型类
泛型类和泛型接口差不多,就是类在定义时声明参数化类型,可以声明多个,逗号隔开。
public class ResultData <E>{
public List<E> result;
//public static List<E> result1; //1:Cannot make a static reference to the non-static type E
ResultData(){
result = new ArrayList<E>();
}
public void addResult(E result) {
this.result.add(result);
}
public E getResult() {
if(result.size() > 0) {
return this.result.remove(0);
}else
return null;
}
public E getResult(int index) {
return this.result.get(index);
}
// public static E getResult1(int index) { //2
// return this.result.get(index);
// }
//泛型函数
public static <T> T[] toArray(T[] param) {
return param;
}
public static void main(String[] args) {
ResultData<String> result1 = new ResultData<String>();
result1.addResult("abc");
ResultData<Integer> result2 = new ResultData<Integer>();
result2.addResult(123);
ResultData<Double> result3 = new ResultData<Double>();
result3.addResult(13.14);
String str = result1.getResult();
System.out.println(str);
Class class2 = result2.getClass();
Class class3 = result3.getClass();
if(class2 == class3) {
System.out.println("泛型类指定不同的具体类型后,是同一个类");
}
}
}
- 泛型类在类上定义的泛型类型(E)不能涉及到静态变量和静态方法,例如1,2编译时会报错Cannot make a static reference to the non-static type E;
- 泛型类里可以使用其他泛型变量的泛型方法,例如toArray,这个方法里面没有设计到类定义到参数类型E,所以可以时static类型的;
- 在指定具体的参数类型后,只是在编译阶段检测类型,在运行的时候,其实时同一个类,类是一样的;
三:泛型方法
泛型方法定义有两种:
- 泛型类定义的泛型类型,在类里面使用该泛型类型的方法;
- 在方法关键字后面,返回类型前面,用<泛型类型变量>的方式先声明泛型类型变量名,方法的参数,返回类型,方法体中可以使用这个泛型类型变量。
public class GenericUtil {
//比较大小,返回三个指中最大的那个
public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
T max = x;
if(y.compareTo(max) > 0) { // y>max
max = y;
}
if(z.compareTo(max) > 0) { //z>max
max = z;
}
return max;
}
public static void main(String[] args) {
int max = GenericUtil.maximum(3, 1, 2);
System.out.println("the max value =" + max);
String max1 = GenericUtil.maximum("china", "apple", "bear");
System.out.println("the max value =" + max1);
}
}
四:泛型通配符
所有的类型对象父类都是Object,在泛型中我们可以使用<?>来通配一类类型。
public class GenericUtil {
public static void print(List<?> list) {
System.out.println("打印内容为:"+list.get(0));
}
public static void main(String[] args) {
List<String> list1 = new ArrayList<String>();
list1.add("hello");
List<Integer> list2 = new ArrayList<Integer>();
list2.add(110);
GenericUtil.print(list1);
GenericUtil.print(list2);
}
}
上面的print方法,打印列表的第一个元素,这里List<?>逻辑上可以看做是List<String>,List<Integer>等的父类。可以用通配符实现一些和具体类型无关的方法。
五:泛型上限和下限
泛型上限 extends
在上面举例泛型方法maximum中,定义泛型类型变量的时候使用了<T extends Comparable<T>>,这里的意思是类型T一定是Comparable类型的子类。还例如:
public class GenericUtil {
public static <T extends Number> void print(T x){
System.out.println("打印内容为:"+x);
}
public static void main(String[] args) {
//GenericUtil.print("String"); //1:Bound mismatch: The generic method print(T) of type GenericUtil is not applicable for the arguments (String).
GenericUtil.print(123);
GenericUtil.print(43.23);
}
}
上面的例子中,print方法的参数继承子Number,所以可以成功打印数值型参数,非数值型的变量例如字符串在编译阶段就会报错Bound dismatch。可以通过泛型上限限制参数类型范围。
泛型下限 super
和泛型上限相反,泛型上限是指定参数类型最低为。例如<T super Number>,print方法的参数类型只能为Number,Object