概述

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的更多相关文章

  1. JDK1.5新特性,基础类库篇,集合框架(Collections)

    集合框架在JDK1.5中增强特性如下: 一. 新语言特性的增强 泛型(Generics)- 增加了集合框架在编译时段的元素类型检查,节省了遍历元素时类型转换代码量. For-Loop循环(Enhanc ...

  2. JDK1.8新特性——使用新的方式遍历集合

    JDK1.8新特性——使用新的方式遍历集合 摘要:本文主要学习了在JDK1.8中新增的遍历集合的方式. 遍历List 方法: default void forEach(Consumer<? su ...

  3. JDK1.7新特性

    jdk1.7新特性 1 对集合类的语言支持: 2 自动资源管理: 3 改进的通用实例创建类型推断: 4 数字字面量下划线支持: 5 switch中使用string: 6 二进制字面量: 7 简化可变参 ...

  4. jdk1.6新特性

    1.Web服务元数据 Java 里的Web服务元数据跟微软的方案基本没有语义上的区别,自从JDK5添加了元数据功能(Annotation)之后,SUN几乎重构了整个J2EE体 系, 由于变化很大,干脆 ...

  5. JDK1.8 新特性

    jdk1.8新特性知识点: Lambda表达式 函数式接口 *方法引用和构造器调用 Stream API 接口中的默认方法和静态方法 新时间日期API https://blog.csdn.net/qq ...

  6. JDK1.6新特性,WebService强化

    Web service是一个平台独立的,松耦合的,自包含的.基于可编程的web的应用程序,可使用开放的XML标准来描述.发布.发现.协调和配置这些应用程序,用于开发分布式的互操作的应用程序. Web ...

  7. JDK1.7新特性(2):异常和可变长参数处理

    异常 jdk1.7对try--catch--finally的异常处理模式进行了增强,下面我们依次来看增强的方面. 1. 为了防止异常覆盖,给Throwable类增加了addSuppressed方法,可 ...

  8. jdk1.8新特性应用之Iterable

    我们继续看lambda表达式的应用: public void urlExcuAspect(RpcController controller, Message request, RpcCallback ...

  9. jdk1.8新特性之方法引用

    方法引用其实就是方法调用,符号是两个冒号::来表示,左边是对象或类,右边是方法.它其实就是lambda表达式的进一步简化.如果不使用lambda表达式,那么也就没必要用方法引用了.啥是lambda,参 ...

随机推荐

  1. 1202: [HNOI2005]狡猾的商人 - BZOJ

    Description 刁姹接到一个任务,为税务部门调查一位商人的账本,看看账本是不是伪造的.账本上记录了n个月以来的收入情况,其中第i 个月的收入额为Ai(i=1,2,3...n-1,n), .当 ...

  2. [转载]async & await 的前世今生

    async 和 await 出现在C# 5.0之后,给并行编程带来了不少的方便,特别是当在MVC中的Action也变成async之后,有点开始什么都是async的味道了.但是这也给我们编程埋下了一些隐 ...

  3. uva 11069

    一开始打了个表  发现 a[i] = a[i-3]+a[i-2]; #include <iostream> #include <fstream> #include <cs ...

  4. Unity3D的几种坐标系

    原地址:http://www.cnblogs.com/martianzone/p/3371789.html http://www.cnblogs.com/88999660/archive/2013/0 ...

  5. socket选项自带的TCP异常断开检测

    TCP异常断开是指在突然断电,直接拔网线等等情况下,如果通信双方没有进行数据发送通信等处理的时候,无法获知连接已经断开的情况. 在通常的情况下,为了使得socket通信不受操作系统的限制,需要自己在应 ...

  6. 1046-第K回文数

    描述 回文数是这样一个正整数:它从左往右读和从右往左读是一样的.例如1,111,121,505都是回文数.将1到100,000,000内所有回文数按从小到达排序后,第k个回文数是多少呢? 输入 第一行 ...

  7. 咦,为DJANGO的ORM的QUERYSET增加数据列的样码,很好用哟

    这个我真的没有查资料,是通过直觉和经验弄出来的,哈哈,感觉用深一点好. 这样在模板输出时,就更好控制啦.. if self.kwargs: if self.kwargs.has_key('search ...

  8. tomcat集群部署

    1.apache只有处理静态事物的能力, 而tomcat的强项就是处理动态的请求 2.由apache作为入口,如果是请求静态页面或者是静态文件,由apache直接提供,如果是请求动态页面,则让apac ...

  9. SQLite入门与分析(四)---Page Cache之事务处理(1)

    写在前面:从本章开始,将对SQLite的每个模块进行讨论.讨论的顺序按照我阅读SQLite的顺序来进行,由于项目的需要,以及时间关系,不能给出一个完整的计划,但是我会先讨论我认为比较重要的内容.本节讨 ...

  10. Java API ——StringBuffer类

    1.StringBuffer类概述 1)我们如果对字符串进行拼接操作,每次拼接,都会构建一个新的String对象,既耗时,又浪费空间.而StringBuffer就可以解决这个问题 2)线程安全的可变字 ...