泛型的优点:

泛型的主要优点就是让编译器保留參数的类型信息,执行类型检查,执行类型转换(casting)操作,编译器保证了这些类型转换(casting)的绝对无误。

/******* 不使用泛型类型 *******/
        List list1 = new ArrayList();
        list1.add(8080);                                  //编译器不检查值
        String str1 = (String)list1.get(0); //需手动强制转换,如转换类型与原数据类型不一致将抛出ClassCastException异常
       
        /******* 使用泛型类型 *******/
        List<String> list2 = new ArrayList<String>();
        list2.add("value");                 //[类型安全的写入数据] 编译器检查该值,该值必须是String类型才干通过编译
        String str2 = list2.get(0); //[类型安全的读取数据] 不须要手动转换

泛型的类型擦除:

Java 中的泛型仅仅存在于编译期。在将 Java 源文件编译完毕 Java 字节代码中是不包括泛型中的类型信息的。使用泛型的时候加上的类型參数,会被编译器在编译的时候去掉。

这个过程就称为类型擦除(type erasure)。

List<String>    list1 = new ArrayList<String>();
        List<Integer> list2 = new ArrayList<Integer>();
       
        System.out.println(list1.getClass() == list2.getClass()); // 输出结果: true
        System.out.println(list1.getClass().getName()); // 输出结果: java.util.ArrayList
        System.out.println(list2.getClass().getName()); // 输出结果: java.util.ArrayList

在以上代码中定义的 List<String> 和 List<Integer> 等类型。在编译之后都会变成 List。而由泛型附加的类型信息对 JVM 来说是不可见的。所以第一条打印语句输出 true,

第二、第三条打印语句都输出 java.util.ArrayList,这都说明 List<String> 和 List<Integer> 的对象使用的都是同一份字节码。执行期间并不存在泛型。

来看一个简单的样例:

package test;

import java.util.List;
/**
 * -----------------------------------------
 * @描写叙述  类型擦除
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 */
public class GenericsApp {

public void method(List<String> list){
       
    }
   
    /*
     * 编译出错,这两个方法不属于重载,因为类型的擦除,使得这两个方法的參数列表的參数均为List类型,
     * 这就相当于同一个方法被声明了两次,编译自然无法通过了
     *
    public void method(List<Integer> list){
       
    }
    */
   
}

以此类为例,在 cmd 中 编译 GenericsApp.java 得到字节码(泛型已经擦除)。然后再反编译这份字节码来看下源代码中泛型是不是真的被擦除了:

从图中能够看出,经反编译后的源代码中 method 方法的參数变成了 List 类型。说明泛型的类型是真的被擦除了。字节码文件里不存在泛型。也就是说。执行期间泛型并不存在,它在

编译完毕之后就已经被擦除了。

泛型类型的子类型:

泛型类型跟其是否是泛型类型的子类型没有不论什么关系。

List<Object> list1;
        List<String> list2;
       
        list1 = list2; // 编译出错
        list2 = list1; // 编译出错

大家都知道,在 Java 中。Object 类是全部类的超类,自然而然的 Object 类是 String 类的超类,按理。将一个 String 类型的对象赋值给一个 Object 类型的对象是可行的。

可是泛型中并不存在这种逻辑,用更通俗的话说,泛型类型跟其是否子类型没有不论什么关系。

泛型中的通配符(?):

因为泛型类型与其子类型存在不相关性,那么在不能确定泛型类型的时候。能够使用通配符(?

)。通配符(?)能匹配随意类型。

List<?> list;
        List<Object> list1 = null;
        List<String>  list2 = null;
       
        list = list1;
        list = list2;

限定通配符的上界:

ArrayList<?

extends Number> collection = null;
       
        collection = new ArrayList<Number>();
        collection = new ArrayList<Short>();
        collection = new ArrayList<Integer>();
        collection = new ArrayList<Long>();
        collection = new ArrayList<Float>();
        collection = new ArrayList<Double>();

? extends XX,XX 类是用来限定通配符的上界,XX 类是能匹配的最顶层的类。它仅仅能匹配 XX 类以及 XX 类的子类。在以上代码中,Number 类的实现类有:

AtomicInteger、AtomicLong、 BigDecimal、 BigInteger、 Byte、 Double、 Float、 Integer、 Long、 Short ,因此以上代码均无错误。

限定通配符的下界:

ArrayList<?

super Integer> collection = null;
       
        collection = new ArrayList<Object>();
        collection = new ArrayList<Number>();
        collection = new ArrayList<Integer>();

? super XX,XX 类是用来限定通配符的下界。XX 类是能匹配的最底层的类,它仅仅能匹配 XX 类以及 XX 类的超类,在以上代码中,Integer 类的超类有:

Number、Object,因此以上代码均能通过编译无误。

通过反射获得泛型的实际类型參数:

这个就有点难度了。上面已经说到,泛型的类型參数会在编译完毕以后被擦除,那在执行期间还怎么来获得泛型的实际类型參数呢?这个是有点难度了吧?似乎不可能实现的样子。

事实上不然。java.lang.Class 类从 Java 1.5 起(假设没记错的话),提供了一个 getGenericSuperclass() 方法来获取直接超类的泛型类型,这就使得获取泛型的实际类型參数成为

了可能,以下来看一段代码。这段代码非常精辟,非常实用,大家一定要学到手哈:

package test;

import java.lang.reflect.ParameterizedType;
/**
 * -----------------------------------------
 * @描写叙述  泛型的实际类型參数
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 */
public class Base<T> {

private Class<T> entityClass;
   
    //代码块,也可将其放置到构造子中
    {
        entityClass =(Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
           
    }
   
    //泛型的实际类型參数的类全名
    public String getEntityName(){
       
        return entityClass.getName();
    }
   
    //泛型的实际类型參数的类名
    public String getEntitySimpleName(){
       
        return entityClass.getSimpleName();
    }

//泛型的实际类型參数的Class
    public Class<T> getEntityClass() {
        return entityClass;
    }
   
}

以上代码的精华全在这句:(Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];

实际上。这句话咋看起来非常难看的明确,理解起来就更加的吃力了,以下容我来将这句复杂的代码拆分开来,理解起来可能会好些:

//代码块,也可将其放置到构造子中
    {
        //entityClass =(Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        try {
            Class<?> clazz = getClass(); //获取实际执行的类的 Class
            Type type = clazz.getGenericSuperclass(); //获取实际执行的类的直接超类的泛型类型
            if(type instanceof ParameterizedType){ //假设该泛型类型是參数化类型
                Type[] parameterizedType = ((ParameterizedType)type).getActualTypeArguments();//获取泛型类型的实际类型參数集
                entityClass = (Class<T>) parameterizedType[0]; //取出第一个(下标为0)參数的值
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
           
    }

注意,获取 Class 实例的时候是用 getClass(),而不是用 Base.class,获取 Class 的方式有三种。这是当中的两种。另一种是 Class.forName("类全名")。如需了解反射的基础知识

请前往上一篇随笔 java 反射基础

那么。Base.class 与 getClass(),这两个方法来获取类的字节码的时候,有什么不一样的地方呢?当然有不一样的地方了,Base.class 是写死了的,它得到的永远是 Base 类的字节码,

而 getClass() 方法则不同,在上面代码凝视中的第一、二行凝视我用了“实际执行的类”6个字,这几个字非常重要,一定要理解,假设无法理解。以下的你可能就看不懂了。

为了方便大家的理解。以下插加一个小样例来加以说明 类.class 与 getClass() 两种方法来获取类的字节码有什么差别:

package test;
/**
 * -----------------------------------------
 * @描写叙述  超类
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 */
public class Father {

public Father (){
       
        System.out.println("Father 类的构造子:");
        System.out.println("Father.class :" + Father.class);
        System.out.println("getClass()      :" + getClass());
    }
   
}

package test;

/**
 * -----------------------------------------
 * @描写叙述  超类的子类(超类的实现类)
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 */
public class Children extends Father{

}

package test;
/**
 * -----------------------------------------
 * @描写叙述  測试类
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 */
public class Test {

public static void main(String[] args){
       
        new Children(); //实际执行的类是Children(Father类的子类或者说是实现类)
    }
   
}

后台打印输出的结果:

Father 类的构造子:
Father.class :class test.Father
getClass()      :class test.Children

从打印出的结果看来。类.class 与 getClass() 的差别非常明了了。getClass() 获取的是实际执行的类的字节码,它不一定是当前类的 Class,有可能是当前类的子类的 Class,详细是哪

个类的 Class。须要依据实际执行的类来确定。new 哪个类,getClass() 获取的就是哪个类的 Class,而 类.class 获取得到的 Class 永远仅仅能是该类的 Class。这点是有非常大的差别的。

这下“实际执行的类”能理解了吧。那么上面的那段被拆分的代码也就不难理解了,getClass() 理解了那 clazz.getGenericSuperclass() 也就没什么问题了吧,千万不要以为

clazz.getGenericSuperclass() 获取得到的是 Object 类那就成了,实际上假如当前执行的类是 Base 类的子类,那么 clazz.getGenericSuperclass() 获取得到的就是 Base 类。

再者就是最后一句。(Class<T>) parameterizedType[0],怎么就知道第一个參数(parameterizedType[0])就是该泛型的实际类型呢?非常easy。因为 Base<T> 的泛型的类型

參数列表中仅仅有一个參数。所以,第一个元素就是泛型 T 的实际參数类型。

其余的已经加了凝视,看一下就明确了,这里不多解释,以下 Base 这个类是不是就直接能使用了呢?来看一下就知道了:

package test;
/**
 * -----------------------------------------
 * @描写叙述  測试类
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 */
public class Test {

public static void main(String[] args){
       
        Base<String> base = new Base<String>();
        System.out.println(base.getEntityClass());                        //打印输出 null
    //    System.out.println(base.getEntityName());                //抛出 NullPointerException 异常
    //    System.out.println(base.getEntitySimpleName()); //抛出 NullPointerException 异常
    }
   
}

从打印的结果来看,Base 类并不能直接来使用,为什么会这样?原因非常easy。因为 Base 类中的 clazz.getGenericSuperclass() 方法,假设随随便便的就确定 Base 类的泛型的类型

參数,则非常可能无法通过 Base 类中的 if 推断,导致 entityClass 的值为 null,像这里的 Base<String>,String 的 超类是 Object,而 Object 并不能通过 if 的推断语句。

Base 类不能够直接来使用,而是应该通过其子类来使用,Base 应该用来作为一个基类,我们要用的是它的详细的子类,以下来看下代码,它的子类也不是随便写的:

package test;
/**
 * -----------------------------------------
 * @描写叙述  Base类的实现类
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 */
public class Child extends Base<Child>{

}

从上面代码来看,Base 的泛型类型參数就是 Base 的子类本身,这样一来,当使用 Base 类的子类 Child 类时,Base 类就能准确的获取到当前实际执行的类的 Class。来看下怎么使用

package test;
/**
 * -----------------------------------------
 * @描写叙述  測试类
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-25 <p>
 * -----------------------------------------
 */
public class Test {

public static void main(String[] args){
       
        Child child = new Child();
        System.out.println(child.getEntityClass());
        System.out.println(child.getEntityName());
        System.out.println(child.getEntitySimpleName());
    }
   
}

后台打印输出的结果:

class test.Child
test.Child
Child

好了,文章接近尾声了,假设你能理解透这个样例。你能够将这个思想运用到 DAO 层面上来。以 Base 类作为全部 DAO 实现类的基类。在 Base 类里面实现数据库的 CURD 等基本

操作,然后再使全部详细的 DAO 类来实现这个基类,那么,实现这个基类的全部的详细的 DAO 都不必再实现数据库的 CURD 等基本操作了,这无疑是一个非常棒的做法。

(通过反射获得泛型的实际类型參数)补充:

泛型反射的关键是获取 ParameterizedType 接口,再调用 ParameterizedType 接口中的 getActualTypeArguments() 方法就可获得实际绑定的类型。

因为去參数化(擦拭法),也仅仅有在 超类(调用 getGenericSuperclass 方法) 或者成员变量(调用 getGenericType 方法)或者方法(调用 getGenericParameterTypes 方法)

像这些有方法返回 ParameterizedType 类型的时候才干反射成功。

上面仅仅谈到超类怎样反射,以下将变量和方法的两种反射补上:

通过方法。反射获得泛型的实际类型參数:

package test;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;

/**
 * -----------------------------------------
 * @描写叙述  測试类
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-26 <p>
 * -----------------------------------------
 */
public class Test {

public static void main(String[] args){
        /**
         * 泛型编译后会去參数化(擦拭法),因此无法直接用反射获取泛型的參数类型
         * 能够把泛型用做一个方法的參数类型。方法能够保留參数的相关信息。这样就能够用反射先获取方法的信息
         * 然后再进一步获取泛型參数的相关信息,这样就得到了泛型的实际參数类型
         */
        try {
            Class<?

> clazz = Test.class; //取得 Class
            Method method = clazz.getDeclaredMethod("applyCollection", Collection.class); //取得方法
            Type[] type = method.getGenericParameterTypes(); //取得泛型类型參数集
            ParameterizedType ptype = (ParameterizedType)type[0];//将其转成參数化类型,因为在方法中泛型是參数,且Number是第一个类型參数
            type = ptype.getActualTypeArguments(); //取得參数的实际类型
            System.out.println(type[0]); //取出第一个元素
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
   
    //声明一个空的方法,并将泛型用做为方法的參数类型
    public void applyCollection(Collection<Number> collection){
       
    }
}

后台打印输出的结果:

class java.lang.Number

通过字段变量,反射获得泛型的实际类型參数:

package test;www.2cto.com

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;

/**
 * -----------------------------------------
 * @描写叙述  測试类
 * @作者  fancy
 * @邮箱  fancydeepin@yeah.net
 * @日期  2012-8-26 <p>
 * -----------------------------------------
 */
public class Test {

private Map<String, Number> collection;
   
    public static void main(String[] args){
       
        try {
           
            Class<?

> clazz = Test.class; //取得 Class
            Field field = clazz.getDeclaredField("collection"); //取得字段变量
            Type type = field.getGenericType(); //取得泛型的类型
            ParameterizedType ptype = (ParameterizedType)type; //转成參数化类型
            System.out.println(ptype.getActualTypeArguments()[0]); //取出第一个參数的实际类型
            System.out.println(ptype.getActualTypeArguments()[1]); //取出第二个參数的实际类型
           
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
   
}

后台打印输出的结果:

class java.lang.String
class java.lang.Number

Java泛型深入理解的更多相关文章

  1. 对java泛型的理解

    正确的应用java泛型的特性可以更好的实现编程的开闭原则(对扩展开放,对修改关闭),这得益于java泛型提供的在程序运行时获取对象声明类型的特性. 静态语言的特性是在程序编译前进行声明,这样程序在编译 ...

  2. java泛型的理解

    总体介绍泛型: 1.泛型是计算机程序中一种重要的思维方式,它将数据结构和算法与数据类型相分离,使得同一套数据结构和算法,能够应用于各种数据类型,而且还可以保证类型安全,提高可读性.在Java中,泛型广 ...

  3. Java泛型深入理解(转载)

    原文地址  http://blog.csdn.net/sunxianghuang/article/details/51982979 泛型之前 在面向对象编程语言中,多态算是一种泛化机制.例如,你可以将 ...

  4. Java:泛型的理解

    本文源自参考<Think in Java>,多篇博文以及阅读源码的总结 前言 Java中的泛型每各人都在使用,但是它底层的实现方法是什么呢,为何要这样实现,这样实现的优缺点有哪些,怎么解决 ...

  5. 对于Java泛型的理解

    源起:查看COLLECIOTNS类 Q1:为什么java需要泛型? 因为java对于对象类型的确认在编译期,那么强制类型转换就可以通过编译,但是运行时的错误却无法避免,那么泛型的存在可以避免强制类型转 ...

  6. 你对Java泛型的理解够深入吗?

    泛型 泛型提供了一种将集合类型传达给编译器的方法,一旦编译器知道了集合元素的类型,编译器就可以对其类型进行检查,做类型约束. 在没有泛型之前: /** * 迭代 Collection ,注意 Coll ...

  7. Java泛型简单理解

    优点1: 没有使用泛型,向list集合中添加非字符串,运行时会报错:类型不匹配 ObjectList.java: package cn.nxl2018; import java.util.ArrayL ...

  8. 关于Java泛型深入理解小总结

    1.何为泛型 首先泛型的本质便是类型参数化,通俗的说就是用一个变量来表示类型,这个类型可以是String,Integer等等不确定,表明可接受的类型,原理类似如下代码 int pattern; //声 ...

  9. java 泛型的理解与应用

    为什么使用泛型? 举个例子: public class GenericTest { public static void main(String[] args) { List list = new A ...

随机推荐

  1. 修改oracle服务器端字符集

    ----设置字符集步聚------- conn /as sysdba; shutdown immediate; startup mount; alter system enable restricte ...

  2. StringBulider与StringBuffer的异同

    相同点:两者的功能都是相同的,没有任何差别. 不同点:StringBulider 不是同步的,也是线程不安全的,当使用多线程处理缓冲区时,不能使用.但是单线程访问的时候效率高,如果是单线程处理缓冲区资 ...

  3. SSM框架搭建(Spring+SpringMVC+MyBatis)与easyui集成并实现增删改查实现

    一.用myEclipse初始化Web项目 新建一个web project: 二.创建包 controller        //控制类 service //服务接口 service.impl //服务 ...

  4. python 常用库整理

    python 常用库整理 GUI 图形界面 Tkinter: Tkinter wxPython:wxPython pyGTK:PyGTK pyQt:pyQt WEB框架 django:django w ...

  5. BootStrap的入门和响应式的使用

    在做前端开发中,其实有百分之四十的时间用来布局写样式,百分之三十用来写JS逻辑交互,百分之三十时间用来测试调bug,可以看的到的是,用在布局+样式的时候会比较多, 所以会有很多的前端框架诞生,例如bo ...

  6. 深入理解js中的apply、call、bind

    概述 js中的apply,call都是为了改变某个函数运行时的上下文环境而存在的,即改变函数内部的this指向. apply() apply 方法传入两个参数:一个是作为函数上下文的对象,另外一个是作 ...

  7. 【SpringMVC】使用Myeclipse创建SpringMVC项目【超详细教程】

    之前一直是使用Eclipse创建Web项目,用IDEA和MyEclipse的创建SpringMVC项目的时候时不时会遇到一些问题,这里把这个过程记录一下,希望能帮助到那些有需要的朋友.我是用的是MyE ...

  8. 《Linux命令行与shell脚本编程大全》第十四章 处理用户输入

    有时还会需要脚本能够与使用者交互.bash shell提供了一些不同的方法来从用户处获得数据, 包括命令行参数,命令行选项,以及直接从键盘读取输入的能力. 14.1 命令行参数 就是添加在命令后的数据 ...

  9. JavaEE中的MVC(二)Xml配置实现IOC控制反转

    毕竟我的经验有限,这篇文章要是有什么谬误,欢迎留言并指出,我们可以一起讨论讨论. 我要讲的是IOC控制反转,然后我要拿它做一件什么事?两个字:"解耦",形象点就是:表明当前类中需要 ...

  10. C语言的scanf函数

    一. 变量的内存分析 1. 字节和地址 1> 内存以“字节为单位”,Oxffc1,Oxffc2,Oxffc3,Oxffc4....都是字节 ,0x表示的是十六进制 2> 不同类型占用的字节 ...