概述

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. C#中Thread.sleep()

    我们可能经常会用到 Thread.Sleep 函数来使线程挂起一段时间.那么你有没有正确的理解这个函数的用法呢?思考下面这两个问题:1.假设现在是 2008-4-7 12:00:00.000,如果我调 ...

  2. 团体程序设计天梯赛-练习集L2-010. 排座位

    L2-010. 排座位 时间限制 150 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 布置宴席最微妙的事情,就是给前来参宴的各位宾客安排座位. ...

  3. 团体程序设计天梯赛-练习集L1-019. 谁先倒

    L1-019. 谁先倒 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 划拳是古老中国酒文化的一个有趣的组成部分.酒桌上两人划拳 ...

  4. The 5th Zhejiang Provincial Collegiate Programming Contest------ProblemK:Kinds of Fuwas

    http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1974 题意:问四个角都有同一个福娃的矩形有多少个. #include<b ...

  5. WPF 视图分组排序

    视图分组排序 效果: 实现步骤: 第一步:为分组做一个标题头,就是效果图中的浅蓝色部分: <DataGrid.GroupStyle>标签部分: <DataGrid x:Name=&q ...

  6. sc.exe管理系统服务

    sc.exe管理系统服务 下面介绍SC,SC QC,and SC QUERY sc.exe create HomerSatelliteDesktopGC binPath= "D:XXXXXX ...

  7. ARMv7 ldr/str指令详解

    因为ARM的算术运算不支持直接操作内存地址,所以要把内存里的数据先加载进寄存器.ldr指令就是干这事的,称为间接取址模式. 一共有3*3九种模式,先是直接偏移,先偏移,后偏移三大类,指的是如何对源操作 ...

  8. 如何完全卸载VS2010

    1.首先用360卸载,当卸载完成后,提示有残余的话,就强力清除 2,接着,下载IobitUninstaller工具 3.按照下面进行卸载 1.Microsoft .NET Framework 4 框架 ...

  9. 【HDOJ】4691 Front compression

    后缀数组基础题目,dc3解. /* 4691 */ #include <iostream> #include <sstream> #include <string> ...

  10. Android开发框架之xUtils学习

    1.一个非作者弄的xUtils API文档: http://xutilsapi.oschina.mopaas.com/overview-summary.html 2.使用xUtils用户的一些博客文档 ...