Java泛型详解
2015-10-23 21:14
405 查看
java泛型的了解
java泛型是1.5的新特性,我们先通过一个例子来简单了解一下泛型。
泛型了解:
//在java 1.5之前,集合是这样处理的。
List c1 = new ArrayList();
c1.add("abc");
c1.add(1);
String s1 = (String) c1.get(0);
String s2 = (String) c1.get(1);
在编译的时候,这样是可以编译通过的,在java1.5之前,但是,在运行的时候会出现错误,因为List中存储的是object,是开发者自己做的类型转化,如上述代码,就把Integer转化成了String。
而在java1.5之后,我们可以这样做。
//在java 1.5之后
List<String> c1 = new ArrayList<String>();
c1.add("abc");
c1.add(1);
这样,使用泛型后,编译的时候是有错误的,说了里面只能存String,结果存了Integer。
简单总结一下:没有使用泛型的时候,只要是对象,不管是什么类型,都可以存储进同一个集合,使用泛型集合后,可以将集合中的元素限定为某一种类型,这样更安全。在从集合中取元素的时候,不需要进行强制转化,编译器已经知道了集合中存的是什么类型。
进一步了解泛型:
泛型的一些术语介绍:
ArrayList<E> ArrayList<String>
整个ArrayList<E>称为泛型类型。中间这个<E>称为类型变量,(类型参数)
整个ArrayList<String>称为参数化的类型,String称为实际类型参数或者类型参数的实例。
ArrayList称为原始类型。
泛型的一些思考:
一:
这样看来,泛型让运行过程中可能出现的错误在编译的过程中暴露出来,更加安全的使用集合,那么,泛型是不是就可以说是给编译器用的,因为,编译器会提示我们写的代码是不是安全的。如果编译完之后,这个泛型还有作用吗?
List<String> c1 = new ArrayList<String>();
List<Integer> c2 = new ArrayList<Integer>();
System.out.println(c1.getClass() == c2.getClass());
看一下打印结果:
true
这说明上面的c1和c2是同一份字节码,因此看来,泛型在编译过后,已经不存在了,这就是“去类型化”。
既然,字节码中已经没有了类型信息的限制,那通过反射,就可以往集合中放入其它类型的实例了。看一下如下代码:
List<String> c1 = new ArrayList<String>();
try {
Method method = c1.getClass().getMethod("add", Object.class);
method.invoke(c1, 123);
Object object = c1.get(0);
System.out.println(object);
} catch (Exception e) {
e.printStackTrace();
}
打印结果:
123
并没有报错,存的是Object,取的是Object。通过去类型化的特性,在只能存string的集合中存了c1.由此,更证明了泛型在编译完之后就消失了,泛型是给编译器看的。
二:
参数化类型和原始类型的兼容性:
//可以编译通过
List<String> c1 = new ArrayList();
//可以编译通过
List c2 = new ArrayList<String>();
//不可以编译通过
List<String> c3 = new ArrayList<Object>();
//不可以编译通过
List<Object> c3 = new ArrayList<String>();
这个很好理解,现在有了泛型,那么前辈们写的代码我们还要继续使用,如一个方法一直返回一个ArrayList,现在来个一个参数化类型,能不能调用这个函数,当然可以,同样,你传递一个参数化的类型,原来接收一个集合,也应该可以使用。 因为,泛型是给编译器用的,编译器肯定兼容之前的,肯定能通过了。参数化类型是不考虑继承关系的,所以,第三种和第四种写法是错误的。
泛型的通配符:
在api文档中,经常会看到这样的“<?>”这样的问号在类的后面跟着。先通过一个需求来了解问号。
定义一个方法,可以打印任意List集合中的元素。(List<Integer>,List<String>......)这样的参数化类型都可以打印。
public void print(List<?> list){
for (Object object : list) {
System.out.println(object);
}
}
? 就是通配符,代表任意类型参数。小例子:
public void method1(Collection<Object> c){
//可以编译通过,add的是Object
c.add("abc");
c.add(123);
//错误,前面说过,参数化类型是不考虑继承的。所以,错误
c = new ArrayList<String>();
}
public void method2(Collection<?> c){
//并没有声明具体类型就放String?错误
c.add("abc");
//把一个?(可以匹配任意类型),让它类型参数实例为String
c = new ArrayList<String>();
}
泛型中通配符的扩展。
1:限定通配符的上边界
List<? extends Number> list = new ArrayList<Integer>();
2;限定通配符下边界
//正确
List<? super Integer> list1 = new ArrayList<Number>();
//错误
List<? super Integer> list2 = new ArrayList<String>();
//正确,可以把一个类型给?
List<?> list1 = new ArrayList<Number>();
//错误,不可以把一个?给一个具体类型
List<Number> list2 = new ArrayList<?>();
自定义泛型:
java的泛型是从c++泛型借鉴过来的,但没有c++的强大,但java还是尽可能的去模仿c++的泛型。
同样,看一个需求,写一个方法,可以实现2个数的相加,但类型不同,先看看c++的实现
template<class T>
T add(T x,T y){
return (T)x+y;
}
在看看java的实现。
/**
* 如何定义一个类型,返回值之前,并紧挨着返回值。
*/
public <T> T add(T x,T y){
//The operator + is undefined for the argument type(s) T, T
//对于T来说,可能没有+这个操作。因此,在java中不能实现c++这样的功能,但仍然了解了java中泛型在方法上的使用。
//return (T)x+y;
return null;
}
泛型的类型推断:
在上面的add方法中,尝试着这样的调用。
add(1, 2);
add(1, 2.0);
add(1, "abc");
这样的调用,是不会出错的,既然参数类型是都是T,然而第2个一个是int,一个是float,第三个一个是int,一个是String都没有报错。通过编辑器自动生成返回值看看。
Integer add = add(1, 2);
Number add2 = add(1, 2.0);
Object add3 = add(1, "abc");
由此可以推断,类型推断中,取了2个参数的共同父类,当作了实际类型参数。
类型推断的传播性:如下
public static <T> void copy(Collection<T> c,T[] a) {
}
//错误
copy(new ArrayList<Float>(), new Integer[10]);
既然类型变量取了父类,为什么这里没有取Number?原因是在对ArrayList参数化的时候,已经指定了T就是Float,这就是类型推断的传播性。
简单总结一下:
a:在返回值或者参数列表中的一个参数中使用了参数变量,那么传递的参数类型就是实际类型参数。
b:在参数列表中多个地方使用了参数变量,在实际调用的过程中,如果传递一样的,很好判断,如果不一样,就会取几个的最大交集,如(Float,Integer,则实际类型参数就是Number)
c:在参数列表多个地方和返回值都使用了,会根据返回值的类型来确定实际类型参数。
d:传播性。
自定义泛型方法&自定义泛型类
1:自定义泛型方法。如上面的add方法一样。
例子:写一个方法,可以交换一个数组中的2个位置上的值。
public static <T> void swap(T[] arr,int x,int y){
T temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
}
试着使用swap这个方法。
//正确
swap(new String[]{"aaa","bbb"},0, 1);
swap(new Integer[]{111,222},0, 1);
//错误
swap(new int[]{1,2}, 0, 1);
注意:泛型的实际类型不能是基本数据类型。因为,数组本来就是一个对象了,不会进行自动装箱,所以这里会报错。并且,数组中的元素不能使用参数化类型。如:
//正确
ArrayList[] a1 = new ArrayList[10];
//错误:Cannot create a generic array of ArrayList<String>
ArrayList<String>[] a2 = new ArrayList<String>[10];
类型变量也可以表示异常。
public static <T extends Exception> void method() throws T{
try {
} catch (Exception e) {
throw (T)e;
}
}
如果有多个类型变量,可以在<>中指出多个,用逗号隔开。如Map集合。
2:自定义泛型类
public class GenericStudy<T> {
}
这样,就可以在方法中使用泛型类型了,因为在实际中,毕竟对一个类的操作希望对应的都是某一种类型,并不是多个,那么就可以在类上定义了,注意:静态方法不能使用这个类型参数,因为对象还没有?哪里来的类型参数实例?静态方法,就自己在方法上定义了。
获取类型参数实例:
由于泛型的去类型化,在编译成字节码后已经看不到这个到底是什么类型了?如何获取?
public void apply(List<String> l){
}
try {
Method method = GenericStudy.class.getMethod("apply", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
ParameterizedType parameterizedType = (ParameterizedType) genericParameterTypes[0];
Type type = parameterizedType.getActualTypeArguments()[0];
Type rawType = parameterizedType.getRawType();
System.out.println(rawType+":"+type);
} catch (Exception e) {
e.printStackTrace();
}
}
打印结果:
interface java.util.List:class java.lang.String
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序
- 二叉查找树
- [原创]java局域网聊天系统