在Java5后推出了泛型,使我们在编译期间操作集合或类时更加的安全,更方便代码的阅读,而让身为编译性语言的Java提供动态性的反射技术,更是在框架开发中大行其道,从而让Java活起来,下面看一下在使用泛型和反射需要注意和了解的事情 

1.Java的泛型是类型擦除的
      Java中的泛型是在编译期间有效的,在运行期间将会被删除,也就是所有泛型参数类型在编译后都会被清除掉.请看以下例子 

Java代码   

  1. publicstaticvoid test(List  testParameter) {  
  2.  
  3. }  
  4.  
  5. publicstaticvoid test(List  testParameter) {  
  6.  
  7. }  

      以上是一个最最简单的重载例子,在编译后泛型类型是会被擦除的,所以无论是List 或List 都会变成List ,所以参数相同,重载不成立,无法编译通过,而且读者可以尝试将不同泛型的类getClass()看看,他们的Class是一样的  

2.不能初始化泛型参数和数组
      请观察以下代码 

Java代码   

  1. class Test  {  
  2. //编译不通过
  3. private T t = new T();  
  4. //编译不通过
  5. private T[] tArray = new T[5];  
  6. //编译通过
  7. private List  list =  new ArrayList ();  
  8. }  

      Java语言是一门强类型,编译型的安全语言,它需要确保运行期的稳定性和安全性,所以在编译时编译器需要严格的检查我们所声明的类型,在编译期间编译器需要获取T类型,但泛型在编译期间已经被擦除了,所以new T()和new T[5]都会出现编译错误,而为什么ArrayList却能成功初始化?这是因为ArrayList表面是泛型,但在编译期间已经由ArrayList内部转型成为了Object,有兴趣的读者可以看一下ArrayList的源码,源码中容纳元素的是private transient Object[] elementData,是Object类型的数组 

3.强制声明泛型的实际类型
      在使用泛型时,在大多数情况下应该声明泛型的类型,例如该集合是存放User对象的,就应该使用List 来声明,这样可以尽量减少类型的转换,若只使用List而不声明泛型,则该集合等同于List 

4.注意泛型没有继承说
      请看以下例子 

Java代码   

  1. publicstaticvoid main(String[] args) {  
  2. // 编译不通过
  3.     List
  4. // or
  5.     List
  6. }  

      上面代码的业务逻辑为有一组元素,但我不确定元素时整形还是浮点型,当我在确定后创建对应的泛型实现类,刚接触泛型的读者有可能会犯以上错误,理解为泛型之间是可以继承的,例如声明Object的泛型实际上创建Integer泛型,错误认为泛型之间也存在继承关系,但这是不正确的,泛型是帮助开发者编译期间类型检查安全.我们可以换种方式实现以上业务逻辑 

Java代码   

  1. publicstaticvoid main(String[] args) {  
  2. //Number 为Integer 和 Double 的公共父类
  3.     List  numberList =  new ArrayList ();  
  4.  
  5. //使用通配符,代表实际类型是Number类型的子类
  6.     Listextends Number> numberList2 = new ArrayList ();  
  7. //or
  8.     Listextends Number> numberList3 = new ArrayList ();  
  9. }  

5.不同场景使用不同的通配符
     泛型当中支持通配符,可以单独使用 '?' 来表示任意类型,也可以使用刚才上面例子中的extends关键字表示传入参数是某一个类或接口的子类,也可以使用super关键字表示接受某一个类型的父类型,extends和super的写法一样,extends是限定上界,super为限定下界 

6.建议采用顺序为List List List 
      以上三者都可以容纳所有的对象,但使用的顺序应该是首选List ,然后List,最后才使用List 

7.使用多重边界限定
      现在有一个业务需求,收钱时需要是我们本公司的员工(继承User),同时亦需要具备收银员的功能(实现Cashier),此时我们当然可以想到接受1个User然后判断User是否实现了Cashier接口,但在泛型的世界中,我们可以怎么做?请看以下例子 

Java代码   

  1. publicstatic   extends User & Cashier>  void CollectMoney(T t){  
  2.  
  3. }  

      以上例子就表明,限定了上界,只要是User和Cashier的子类才可以作为参数传递到方法当中 

======================我是泛型和反射的分界线====================

8.注意Class类的特殊性
      Java语言是先把Java源文件编译成后缀为class的字节码文件,然后通过ClassLoader机制把类文件加载到内存当中,最后生成实例执行的,在描述一个类是,Java使用了一个元类来对类进行描述,这就是Class类,他是一个描述类的类,所以注定Class类是特殊的 
      1.Class类无构造函数,Class对象加载时由JVM通过调用类加载器的 
        defineClass方法来构造Class对象 

      2.Class类还可以描述基本数据类型,由于基本类型并不是Java中的对象,它们 
        一般存在于栈,但Class仍然可以对它们进行描述,例如使用int.class 

      3.其对象都是单例模式,一个Class的实现对象描述一个类,并且只描述一个类 
        所以只要是该被描述的类所有对象都只有一个Class实例 

      4.Class类是Java反射的入口 

9.适时选择getDeclaredXXX和getXXX
      Class类中提供了很多getDeclaredXXX和getXXX的方法,请看以下例子 

Java代码   

  1. publicstaticvoid main(String[] args) {  
  2.     Class  cls = User. class;  
  3. //获取类方法
  4.     cls.getDeclaredMethods();  
  5.     cls.getMethods();  
  6. //获取类构造函数
  7.     cls.getDeclaredConstructors();  
  8.     cls.getConstructors();  
  9. //获取类属性
  10.     cls.getDeclaredFields();  
  11.     cls.getFields();  
  12. }  

      getXXX的方式是获取所有公共的(public)级别的,包括从父类继承的方法,而getDeclaredXXX的方式是获取所有的,包括公共的(public),私有的(private),不受限与访问权限, 

10.反射访问属性或方法时将Accessible设置为true
      在调用构造函数或方法的invoke前检查accessible已经是公认的写法,例如以下代码 

Java代码   

  1. publicstaticvoid main(String[] args) throws Exception {  
  2.     Class  cls = User. class;  
  3. //创建对象
  4.     User user = cls.newInstance();  
  5.  
  6. //获取test方法
  7.     Method method = cls.getDeclaredMethod("test");  
  8.  
  9. //检查Accessible属性
  10. if(!method.isAccessible()){  
  11.         method.setAccessible(true);  
  12.     }  
  13.     method.invoke(user);  
  14. }  

      读者可以尝试获取Class的getMethod,也就是公开的方法,再输出isAccessible,可以看到输出的其实也是false,其实因为accessible属性的语义并不是我们理解的访问权限,而是指是否进行安全检查,而安全监察是非常消耗资源的,所以反射提供了Accessible可选项,让开发者逃避安全检查,有兴趣的读者可以查看AccessibleObject类观察其源码了解安全检查. 

11.使用forName动态加载类
      forName相信各位读者不会陌生,在使用JDBC时要动态加载数据库驱动就是使用forName的方式进行加载,同时亦可以从外部配置文件中读取类的全路径字符串进行加载,在使用forName时,被加载的类就会被加载到内存当中,只会加载类,并不会执行任何代码,而我们的数据库驱动就是利用static代码块来执行操作的,因为当类被加载到内存中时,会执行static代码块 

12.使用反射让模板方法更强大
      模板方法的定义是,定义一个操作的算法骨架,将某些步骤延迟到子类当中实现,而实现细节由子类决定,父类只决定骨架,以下是一个传统模板方法的事例 

Java代码   

  1. publicabstractclass Test {  
  2.  
  3. publicfinalvoid doSomething() {  
  4.         System.out.println("start...");  
  5.         doInit();  
  6.         System.out.println("end.....");  
  7.     }  
  8.  
  9. protectedabstractvoid doInit();  
  10. }  

    此时子类只需要继承Test类实现doInit()方法即可嵌入到doSomething中,现在我们有一个需求,若我在doSomething中需要调用一系列的方法才能完成doSomething呢?而且我调用方法的数量并不确定,只需遵从某些规则则可将方法添加到doSomething方法当中.请看以下代码 

Java代码   

  1. publicabstractclass Test {  
  2.  
  3. publicfinalvoid doSomething() throws Exception {  
  4.  
  5.         Method[] methods = this.getClass().getDeclaredMethods();  
  6.         System.out.println("start...");  
  7. for (Method method : methods) {  
  8. if (this.checkInitMethod(method)) {  
  9.                 method.invoke(this);  
  10.             }  
  11.         }  
  12.         System.out.println("end.....");  
  13.     }  
  14.  
  15. privateboolean checkInitMethod(Method method) {  
  16. // 方法名初始是否为init
  17. return method.getName().startsWith("init")  
  18. // 是否为public修饰
  19.                 && Modifier.isPublic(method.getModifiers())  
  20. // 返回值是否为void
  21.                 && method.getReturnType().equals(Void.TYPE)  
  22. // 是否没有参数
  23.                 && !method.isVarArgs()  
  24. // 是否抽象类型
  25.                 && !Modifier.isAbstract(method.getModifiers());  
  26.     }  
  27. }  

      看到上面的代码,读者是否有似曾相识的感觉?在使用Junit3时是不是只要遵守方法的签名约定,就能成为测试类?使用这种反射可以让模板方法更强大,下次需要使用多个方法在模板方法中时,不要创建多个抽象方法,尝试使用以上方式 

13.不要过分关注反射的效率
      反射的效率是一个老生常谈的问题,普通的调用方法,创建类,在反射的情况下需要调用诸多API才能实现,效率当然要比普通情况下低下,但在项目当中真正引起性能问题的地方,绝大数不会是由反射引起的,而反射带给我们的却是如此的美妙,当今的各系列开源框架,几乎都存在反射的身影,而且大量存在更比比皆是,让Java这个沉睡的美女活起来的,非反射莫属 

总结: 
      笔者在本文章中只从《改善Java程序的151建议》中提取部分进行归纳性叙述,推荐各位读者购买这本书,该书不仅从事例中学习,而且涉及到原理,底层的实现,不仅告诉你应该怎么做,还告诉你为什么要这样做. 

Java高质量代码之 — 泛型与反射的更多相关文章

  1. 编写高质量代码改善java程序的151个建议——导航开篇

    2014-05-16 09:08 by Jeff Li 前言 系列文章:[传送门] 下个星期度过这几天的奋战,会抓紧java的进阶学习.听过一句话,大哥说过,你一个月前的代码去看下,慘不忍睹是吧.确实 ...

  2. 编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则)

    编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则) 目录 建议1: 不要在常量和变量中出现易混淆的字母 建议2: 莫让常量蜕变成变量 建议3: 三元操作符的类型务 ...

  3. Github即将破百万的PDF:编写高质量代码改善JAVA程序的151个建议

    在通往"Java技术殿堂"的路上,本书将为你指点迷津!内容全部由Java编码的最佳 实践组成,从语法.程序设计和架构.工具和框架.编码风格和编程思想等五大方面,对 Java程序员遇 ...

  4. 编写高质量代码:改善Java程序的151个建议(第二章:基本类型)

    编写高质量代码:改善Java程序的151个建议(第二章:基本类型) 目录 建议21:用偶判断,不用奇判断 建议22:用整数类型处理货币 建议23:不要让类型默默转换 建议24:边界还是边界 建议25: ...

  5. 编写高质量代码:改善Java程序的151个建议 --[106~117]

    编写高质量代码:改善Java程序的151个建议 --[106~117] 动态代理可以使代理模式更加灵活 interface Subject { // 定义一个方法 public void reques ...

  6. 博友的 编写高质量代码 改善java程序的151个建议

    编写高质量代码 改善java程序的151个建议 http://www.cnblogs.com/selene/category/876189.html

  7. 编写高质量代码改善java程序的151个建议——[1-3]基础?亦是基础

    原创地址:   http://www.cnblogs.com/Alandre/  (泥沙砖瓦浆木匠),需要转载的,保留下! Thanks The reasonable man adapts himse ...

  8. 编写高质量代码:改善Java程序的151个建议 --[117~128]

    编写高质量代码:改善Java程序的151个建议 --[117~128] Thread 不推荐覆写start方法 先看下Thread源码: public synchronized void start( ...

  9. 编写高质量代码:改善Java程序的151个建议 --[78~92]

    编写高质量代码:改善Java程序的151个建议 --[78~92] HashMap中的hashCode应避免冲突 多线程使用Vector或HashTable Vector是ArrayList的多线程版 ...

随机推荐

  1. java一位数组求平均值,小数

    package com.c2; //注意数据类型,float public class Col {// public static void main(String[] args) { float m ...

  2. juc线程池原理(二):ThreadPoolExecutor的成员变量介绍

    概要 线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析ThreadPoolExecutor类,来了解线程池的原理. ThreadPoolExecutor数据结构 Thread ...

  3. SpringBoot中通过SpringBootServletInitializer如何实现容器初始化

    相关文章 <Servlet3.0之四:动态注册和Servlet容器初始化> <SpringBoot中通过SpringBootServletInitializer如何实现组件加载> ...

  4. easyui-combotree 只能选叶子未级

    easyui-combotree 只能选叶子未级 function edit_dg() { //选中一行,获取这一行的属性的值 var selected = $('#tbClientListBrows ...

  5. 1041 Be Unique

    题意:找到一串数字序列中首个出现的不重复的数字. 思路:用哈希,因为数值大小在[1,10^4],所以可以直接开数组.输入数据时记录每个数字出现过的次数.然后遍历原序列,遇到第一个次数为1的数字就是所求 ...

  6. 关于 Mybatis的原生连接池 和 DBCP 连接池

    一 遇到的问题:  项目用的play框架,数据库DB2, 持久化框架是Mybatis, 连接池用的是Mybatis原生的,遇到的问题是:有时候抛出如下异常: play.api.UnexpectedEx ...

  7. Java学习之SpringBoot整合SSM Demo

    背景:在Java Web中Spring家族有着很重要的地位,之前JAVA开发需要做很多的配置,一堆的配置文件和部署调试一直是JavaWeb开发中的一大诟病,但现在Spring推出了SpringBoot ...

  8. windows重启mysql命令

    开始->运行->cmd 停止:net stop mysql 启动:net start mysql 前提MYSQL已经安装为windows服务

  9. 提高ListView的效率

    按照普通的写法,如果ListView里面有比较多的东西的话,在加载每一个Item的时候,是非常非常卡的.具体表现就是滚动起来的时候会看见明显的卡顿. 关键还是处理自定义Adapter里面的getVie ...

  10. ffmpeg相关时间概念

    v_rescale_q用于计算Packet的PTS.av_rescale_q的返回值是一个很大的整数,且每次计算的结果间隔很大. 不同于avcodec_encode_video改变AVCodecCon ...