JDK1.5新特性(六)……Generics
概述
Generics - This long-awaited enhancement to the type system allows a type or method to operate on objects of various types while providing compile-time type safety. It adds compile-time type safety to the Collections Framework and eliminates the drudgery of casting.
泛型的出现主要是为了解决集合类元素的类型规范,即将集合类参数化,传入规定的元素类型参数,规范其元素的类型,这样就避免了很多类型转换上的异常,下面我们由浅入深,慢慢来介绍
泛型的简单应用
虽然泛型的底层实现略有不爽,但是在表层的使用上还是很好理解的,至少,一些简单的使用就可以解决一大部分问题
下面我们来看一下
过去我们使用集合类来存储和获取对象时是这样做的
1: public static void main(String[] args) {
2: // TODO Auto-generated method stub
3:
4: //过去使用集合类的方法
5: List list = new ArrayList();
6: list.add(1);
7: list.add(2);
8: list.add("1");
9: Integer retVal = (Integer)list.remove(2);
10: System.out.println(retVal);
11: }
我们看到,过去必须手动的进行类型转换,这样就很容易出现ClassCastException异常
加入泛型之后,我们这样做
1: public static void main(String[] args) {
2: // TODO Auto-generated method stub
3:
4: //JDK1.5之后使用集合类的方法
5: List<Integer> list = new ArrayList<Integer>();
6: list.add(1);
7: list.add(2);
8: list.add("1");
9: Integer retVal = list.remove(2);
10: System.out.println(retVal);
11:
12: }
可以看到,List之后尖括号中存放了规定的元素类型,这样只能将Integer对象作为元素传入到List集合中,而当出现其他类型时,编译会出错,这样就有效的将运行时的异常转变为了编译时期的错误,提高了系统的安全性
了解泛型
涉及到的术语
上面的例子中
List和ArrayList被称为原始类型(raw type)
而List<Integer>和ArrayList<Integer>被称为参数化的类型(parameterized type)
其中Integer叫做实际类型参数
原始类型与参数化类型的兼容性
List<Integer> list = new ArrayList();//参数化类型可以接收一个原始类型对象
List l = new ArrayList<String>();//原始类型对象可以接收一个参数化类型对象
之所以会出现这种情况,我想与其底层实现不无关联,等下会提到
看一个例子
1: public static void main(String[] args) {
2: // TODO Auto-generated method stub
3:
4: //JDK1.5之后使用集合类的方法
5: List<Integer> list = new ArrayList();
6: List l = new ArrayList<String>();
7: list.add(1);
8: list.add(2);
9: l.add(1);
10: Integer retVal = (Integer)l.remove(0);
11: System.out.println(retVal);
12:
13: }
由这个例子可知,实际参数只定义在右边是没有什么意义的,在编译阶段,如果左边是原始类型,编译器是不会判断传入的类型的,如果左边是参数化类型,才会判断
参数化类型并不支持类型的继承关系
也就是说
List<Integer> l = new ArrayList<Object>();
List<Object> l = new ArrayList<Integer>();
两种编译器都不支持,两个类型mismatch.
但是如果是这样定义的,就不会报错
List l = new ArrayList<Object>();
List<Integer> l = l;
为什么会是这样?原因是泛型的实现机制:
因为泛型的语法判断是在编译阶段,泛型的定义只保留在编译阶段,在真正的运行阶段,会将泛型的定义擦除,也就是说List<Integer> 与List<String>在底层其实是共用的一份字节码,所以在Java中的泛型实现跟C++中的模板是有本质区别的,那么为什么不保留泛型定义到运行阶段呢?这是因为Java是解释型语言,编译器生成的字节码文件是可以跨平台的,在不同的平台对应不同的JVM,JVM会将同一份字节码翻译成针对不同平台的二进制指令,那么如果将泛型保留到运行时期,JVM需要做大量的指令集的重构,这个工程非常的浩大,同时,也是为了兼容原来的原始类型,这是在设计历史上必须承担的后果,所以,这种的办法就是将泛型只保留在编译时期,让编译器判断语法正误,而在运行时期,进行类型擦除,当然,在泛型的实现上有很多不够优雅的地方,在业内也褒贬不一,我想这也是任何有历史的编程语言都需要承担的吧。
Java不支持定义参数化类型数组
如
Vector<Integer> vectorList[] = new Vector<Integer>[10]//这样会报错
泛型的定义
参数化类型其实就是将一种数据类型也作为一个参数传递给一个原始类型,那么我们也可以定义自己的带有泛型参数的东西
泛型类的定义
什么时候定义泛型类?
当类中要操作的某些参数的数据类型不确定时,JDK1.5之前是使用Object定义,例如Ojbect的toString方法
JDK1.5之后我们可以用泛型来定义,通常用单个的大写字母表示一个泛型
1: public class MyGenericClass<T> {
2: private T x;
3: private T y;
4:
5: public T getX() {
6: return x;
7: }
8: public void setX(T x) {
9: this.x = x;
10: }
11: public T getY() {
12: return y;
13: }
14: public void setY(T y) {
15: this.y = y;
16: }
17: }
1: public class GenericsTest {
2:
3: /**
4: * @param args
5: */
6: public static void main(String[] args) {
7: // TODO Auto-generated method stub
8: MyGenericClass<String> mgc = new MyGenericClass<String>();
9: mgc.setX("x");
10: mgc.setY("y");
11: System.out.println(mgc.getX());
12: System.out.println(mgc.getY());
13: }
14: }
泛型方法的定义
当类中不同方法操作的数据类型不同时,我们可以将泛型定义在方法上
1: public class MyGenericClass<T> {
2: private T x;
3: private T y;
4:
5: public T getX() {
6: return x;
7: }
8: public void setX(T x) {
9: this.x = x;
10: }
11: public T getY() {
12: return y;
13: }
14: public void setY(T y) {
15: this.y = y;
16: }
17:
18: public static <E extends Comparable<E>> E max(E a,E b){
19: if(a.compareTo(b) > 0)
20: return a;
21: else
22: return b;
23: }
24: }
1: public class GenericsTest {
2:
3: /**
4: * @param args
5: */
6: public static void main(String[] args) {
7: // TODO Auto-generated method stub
8: MyGenericClass<String> mgc = new MyGenericClass<String>();
9: mgc.setX("x");
10: mgc.setY("y");
11: System.out.println(mgc.getX());
12: System.out.println(mgc.getY());
13:
14: //调用静态方法max
15: String max = MyGenericClass.max("123", "456");
16: System.out.println(max);
17: }
18: }
上面的例子中,max静态方法定义的泛型E与泛型类中定义的泛型T不同,只在max方法上适用,至于extends范围限定,等下会提到
?通配符
?通配符不管是在泛型的定义或者使用上,都应用非常广泛
它表示不确定的泛型类型
例如:
1: public class GenericsTest2 {
2:
3: /**
4: * @param args
5: */
6: public static void main(String[] args) {
7: // TODO Auto-generated method stub
8: print(new ArrayList<String>());
9: }
10: private static void print(Collection<?> col){
11: Iterator<?> it = col.iterator();
12: while(it.hasNext()){
13: System.out.println(it.next());
14: }
15: }
16: }
这样的代码,当我们不确定要传入的实际类型的时候,可以用?作为占位符,表示该类型不确定,那么它的弊端也是显而易见的,就是不能调用类型的特有方法
比如
System.out.println(it.next().length())//这样是不允许的,因为传入的参数类型可能是数组类型
? i = it.next();//这样是不允许的
这也就引出了?与T在定义时的区别
?与T都表示不确定的参数类型,他们有同样的弊端,就是不能调用类型的特有方法
但是T至少可以用作引用,比如 T i = it.next();,这样是可以的,但是定义?的时候就不行了
泛型限定
<? extends E>:可以接收E类型或者E的子类型,即设置上限
<? super E>:可以接收E类型或者E的父类型,即设置下限
1: public class GenericsTest2 {
2:
3: /**
4: * @param args
5: */
6: public static void main(String[] args) {
7:
8: //String并不是Number的子类,所以并不能作为参数传递过去
9: print(new ArrayList<String>());
10: }
11: private static void print(Collection<? extends Number> col){
12: Iterator<?> it = col.iterator();
13: while(it.hasNext()){
14: System.out.println(it.next());
15: }
16: }
17: }
上面的例子中,由于print方法定义了?通配符的范围,规定该泛型必须为Number或者Number的子类,String并不是Number的子类,所以并不能作为参数传递过去
通过反射获取泛型的实际类型参数
由于泛型的类型擦除机制,我们在运行过程中是无法从集合类本身获取类型参数的,所以只能从带有这些泛型类参数的方法中发射获取
1: public class GetParameterdTypeByReflect {
2:
3: /**
4: * @param args
5: * @throws NoSuchMethodException
6: * @throws SecurityException
7: */
8: public static void main(String[] args) throws SecurityException, NoSuchMethodException {
9:
10: //获取参数列表中带有泛型类的方法对象
11: Method method = GetParameterdTypeByReflect.class.getMethod("applyMethod", ArrayList.class,Collection.class);
12:
13: //得到方法参数中带有泛型的参数类型
14: Type[] types = method.getGenericParameterTypes();
15:
16: //遍历这些类型
17: for(Type type : types){
18: //将Type转为ParameterizedType(参数化类型)
19: ParameterizedType pType = (ParameterizedType)type;
20: //打印参数化类型名称
21: System.out.println(pType);
22: //打印参数化类型的原始类型
23: System.out.println(pType.getRawType());
24: //打印参数化类型的实际类型参数列表
25: for(int i = 0 ; i < pType.getActualTypeArguments().length ; i ++){
26: System.out.println(pType.getActualTypeArguments()[i]);
27: }
28: }
29: }
30:
31: public static void applyMethod(ArrayList<String> arrayList,Collection<?> col){
32:
33: }
34: }
JDK1.5新特性(六)……Generics的更多相关文章
- JDK1.5新特性,基础类库篇,集合框架(Collections)
集合框架在JDK1.5中增强特性如下: 一. 新语言特性的增强 泛型(Generics)- 增加了集合框架在编译时段的元素类型检查,节省了遍历元素时类型转换代码量. For-Loop循环(Enhanc ...
- JDK1.8新特性——使用新的方式遍历集合
JDK1.8新特性——使用新的方式遍历集合 摘要:本文主要学习了在JDK1.8中新增的遍历集合的方式. 遍历List 方法: default void forEach(Consumer<? su ...
- JDK1.7新特性
jdk1.7新特性 1 对集合类的语言支持: 2 自动资源管理: 3 改进的通用实例创建类型推断: 4 数字字面量下划线支持: 5 switch中使用string: 6 二进制字面量: 7 简化可变参 ...
- jdk1.6新特性
1.Web服务元数据 Java 里的Web服务元数据跟微软的方案基本没有语义上的区别,自从JDK5添加了元数据功能(Annotation)之后,SUN几乎重构了整个J2EE体 系, 由于变化很大,干脆 ...
- JDK1.8 新特性
jdk1.8新特性知识点: Lambda表达式 函数式接口 *方法引用和构造器调用 Stream API 接口中的默认方法和静态方法 新时间日期API https://blog.csdn.net/qq ...
- JDK1.6新特性,WebService强化
Web service是一个平台独立的,松耦合的,自包含的.基于可编程的web的应用程序,可使用开放的XML标准来描述.发布.发现.协调和配置这些应用程序,用于开发分布式的互操作的应用程序. Web ...
- JDK1.7新特性(2):异常和可变长参数处理
异常 jdk1.7对try--catch--finally的异常处理模式进行了增强,下面我们依次来看增强的方面. 1. 为了防止异常覆盖,给Throwable类增加了addSuppressed方法,可 ...
- jdk1.8新特性应用之Iterable
我们继续看lambda表达式的应用: public void urlExcuAspect(RpcController controller, Message request, RpcCallback ...
- jdk1.8新特性之方法引用
方法引用其实就是方法调用,符号是两个冒号::来表示,左边是对象或类,右边是方法.它其实就是lambda表达式的进一步简化.如果不使用lambda表达式,那么也就没必要用方法引用了.啥是lambda,参 ...
随机推荐
- VS asp.net 连接64位oracle 11g
vs2010 vs2013 vs2015 无法连接oracle 11g 64bit 尝试加载 Oracle 客户端库时引发 BadImageFormatException......... A.安装o ...
- Windows下配置使用 MemCached
Windows下配置使用MemCached 工具: memcached-1.2.6-win32-bin.zip MemCached服务端程序(for win) Memcached Manage ...
- ExtJS4.2学习(20)动态数据表格之前几章总结篇1(转)
鸣谢:http://www.shuyangyang.com.cn/jishuliangongfang/qianduanjishu/2014-02-18/196.html --------------- ...
- 深入理解c++中char*与wchar_t*与string以及wstring之间的相互转换
本篇文章是对c++中的char*与wchar_t*与string以及wstring之间的相互转换进行了详细的分析介绍,需要的朋友参考下-复制代码 代码如下: #ifndef USE_H_ ...
- WinDbg调试DMP格式文件
前言:WinDbg是微软开发的免费源代码级的调试工具.WinDbg可以用于Kernel模式调试和用户模式调试,还可以调试Dump文件.本文的讨论是在安装了Debugging Tools for Win ...
- c++ string assign =
C++ string类的成员函数,用于拷贝.赋值操作,它们允许我们顺次地把一个string 对象的部分内容拷贝到另一个string 对象上. string &operator=(const s ...
- winform学习日志(十九)----------真正三层架构之登录
摘要:一:三层构架的基础知识在项目开发的过程中,有时把整个项目分为三层架构,其中包括:表示层(UI).业务逻辑层(BLL)和数据访问层(DAL).三层的作用分别如下: 表示层:为用户提供交互操作界面, ...
- IntelliJ IDEA 添加jar包的三种方式
一.直接复制:(不推荐)方法:直接将硬盘上的jar包复制粘贴到项目的lib目录下.
- R语言学习笔记:生成序列(Genenrating Sequences)
R提供了多种生成不同类型序列的方法.如: > x<-1:20 > x [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1 ...
- html元素elem.style.top.left始终为空
有如下元素: <div id="div1" >div1</div> #div1{ width:100px; height:100px; position ...