你的位置:首页 > Java教程

[Java教程]编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议102~105)


建议102:适时选择getDeclaredXXX和getXXX

  Java的Class类提供了很多的getDeclaredXXX方法和getXXX方法,例如getDeclaredMethod和getMethod成对出现,getDeclaredConstructors和getConstructors也是成对出现,那这两者之间有什么差别呢?看如下代码:

public class Client102 {  public static void main(String[] args) throws NoSuchMethodException,      SecurityException {    // 方法名称    String methodName = "doStuff";    Method m1 = Foo.class.getDeclaredMethod(methodName);    Method m2 = Foo.class.getMethod(methodName);  }  //静态内部类  static class Foo {    void doStuff() {    }  }}

  此段代码运行后输出如下:

Exception in thread "main" java.lang.NoSuchMethodException: com.study.advice102.Client102$Foo.doStuff()  at java.lang.Class.getMethod(Class.java:1622)  at com.study.advice102.Client102.main(Client102.java:10)

  该异常是说m2变量的getMethod方法没有找到doStuff方法,明明有这个方法呀,为什么没有找到呢?这是因为getMethod方法获得的是所有public访问级别的方法,包括从父类继承的方法,而getDeclaredMethod获得的是自身类的方法,包括公用的(public)方法、私有(private)方法,而且不受限于访问权限。

  其它的getDeclaredConstructors和getConstructors、getDeclaredFileds和getFields等于此相似。Java之所以如此处理,是因为反射本意只是正常代码逻辑的一种补充,而不是让正常代码逻辑发生翻天覆地的变化,所以public的属性和方法最容易获取,私有属性和方法也可以获取,但要限定本类。

  那么问题来了:如果需要列出所有继承自父类的方法,该如何实现呢?简单,先获得父类,然后使用getDeclaredMethods,之后持续递归即可。

建议103:反射访问属性或方法时将Accessible设置为true

  Java中通过反射执行一个方法的过程如下:获取一个方法对象,然后根据isAccessible返回值确定是否能够执行,如果返回值为false则需要调用setAccessible(true),最后再调用invoke执行方法,具体如下: 

    Method method= ...;    //检查是否可以访问    if(!method.isAccessible()){      method.setAccessible(true);    }    //执行方法    method.invoke(obj, args);

 

  此段代码已经成了习惯用法:通过反射方法执行方法时,必须在invoke之前检查Accessible属性。这是一个好习惯,也确实该如此,但方法对象的Accessible属性并不是用来决定是否可以访问的,看如下代码:

public class Foo {  public final void doStuff(){    System.out.println("Do Stuff...");  }}

  定义一个public类的public方法,这是一个没有任何限制的方法,按照我们对Java语言的理解,此时doStuff方法可以被任何一个类访问。我们编写一个客户端类来检查该方法是否可以反射执行:

public static void main(String[] args) throws NoSuchMethodException,      SecurityException, IllegalAccessException,      IllegalArgumentException, InvocationTargetException {    // 反射获取方法    Method m = Foo.class.getMethod("doStuff");    // 打印是否可以访问    System.out.println("Accessible:" + m.isAccessible());    // 执行方法    m.invoke(new Foo());  }

  很简单的反射操作,获得一个方法,然后检查是否可以访问,最后执行方法输出。让我们来猜想一下结果:因为Foo类是public的,方法也是public的,全部都是最开放的访问权限Accessible也应该等于true。但是运行结果却是:

  Accessible:false
      Do Stuff...

  为什么Accessible属性会等于false?而且等于false还能执行?这是因为Accessible的属性并不是我们语法层级理解的访问权限,而是指是否更容易获得,是否进行安全检查。

  我们知道,动态修改一个类或执行方法时都会受到Java安全体制的制约,而安全的处理是非常耗资源的(性能非常低),因此对于运行期要执行的方法或要修改的属性就提供了Accessible可选项:由开发者决定是否要逃避安全体系的检查。

  阅读源代码是最好的理解方式,我们来看AccessibleObject类的源代码,它提供了取消默认访问控制检查的功能。首先查看isAccessible方法,代码如下:

public class AccessibleObject implements AnnotatedElement {   //定义反射的默认操作权限suppressAccessChecks   static final private java.security.Permission ACCESS_PERMISSION =    new ReflectPermission("suppressAccessChecks");   //是否重置了安全检查,默认为false   boolean override;   //构造函数   protected AccessibleObject() {}   //是否可以快速获取,默认是不能   public boolean isAccessible() {    return override;  } }

  AccessibleObject是Filed、Method、Constructor的父类,决定其是否可以快速访问而不进行访问控制检查,在AccessibleObject类中是以override变量保存该值的,但是具体是否快速执行时在Method的invoke方法中决定的,源码如下:

public Object invoke(Object obj, Object... args)    throws IllegalAccessException, IllegalArgumentException,      InvocationTargetException  {    //见擦汗是否可以快速获取,其值是父类AccessibleObject的override变量    if (!override) {     //不能快速获取,执行安全检查        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {        Class<?> caller = Reflection.getCallerClass(1);        checkAccess(caller, clazz, obj, modifiers);      }    }    MethodAccessor ma = methodAccessor;       // read volatile    if (ma == null) {      ma = acquireMethodAccessor();    }    //直接执行方法    return ma.invoke(obj, args);  }

   看了这段代码,大家就清楚了:Accessible属性只是用来判断是否需要进行安全检查的,如果不需要则直接执行,这就可以大幅度的提升系统性能了(当然了,取消了安全检查,也可以运行private方法、访问private属性的)。经过测试,在大量的反射情况下,设置Accessible为true可以提高性能20倍左右。

  AccessibleObject的其它两个子类Field和Constructor与Method的情形类似:Accessible属性决定Field和Constructor是否受访问控制检查。我们在设置Field或执行Constructor时,务必要设置Accessible为true,这并不仅仅是因为操作习惯的问题,还是为我们的系统性能考虑。

 

 建议104:使用forName动态加载类文件

  动态加载(Dynamic Loading)是指在程序运行时加载需要的类库文件,对Java程序来说,一般情况下,一个类文件在启动时或首次初始化时会被加载到内存中,而反射则可以在运行时再决定是否需要加载一个类,比如从Web上接收一个String参数作为类名,然后在JVM中加载并初始化,这就是动态加载,此动态加载通常是通过Class.forName(String)实现的,只是这个forName方法到底是什么意思呢?

  我们知道一个类文件只有在被加载到内存中才可能生成实例对象,也就是说一个对象的生成必然会经过两个步骤:

  • 加载到内存中生成Class的实例对象
  • 通过new关键字生成实例对象

   如果我们使用的是import关键字产生的依赖包,JVM在启动时会自动加载所有的依赖包的类文件,这没有什么问题,如果好动态加载类文件,就要使用forName的方法了,但问题是我们为什么要使用forName方法动态加载一个类文件呢?那是因为我们不知道生成的实例对象是什么类型(如果知道就不用动态加载),而且方法和属性都不可访问呀。问题又来了:动态加载的意义在什么地方呢?

  意义在于:加载一个类即表示要初始化该类的static变量,特别是static代码块,在这里我们可以做大量的工作,比如注册自己,初始化环境等,这才是我们要重点关注的逻辑,例如如下代码: 

package com.study.advice103;public class Client103 {  public static void main(String[] args) throws ClassNotFoundException {    //动态加载    Class.forName("com.study.advice103.Utils");  }}class Utils{  //静态代码块  static{    System.out.println("Do Something.....");  }}

  注意看Client103类,我们并没有对Utils做任何初始化,只是通过forName方法加载了Utils类,但是却产生了一个“Do Something.....”的输出,这就是因为Utils类加载后,JVM会自动初始化其static变量和static静态代码块,这是类加载机制所决定的。

  对于动态加载,最经典的应用是数据库驱动程序的加载片段,代码如下:

    //加载驱动    Class.forName("com.mysql..jdbc.Driver");    String url="jdbc:mysql://localhost:3306/db?user=&password=";    Connection conn =DriverManager.getConnection(url);    Statement stmt =conn.createStatement();

  在没有Hibernate和Ibatis等ORM框架的情况下,基本上每个系统都会有这么一个JDBC链接类,然后提供诸如Query、Delete等的方法,大家有没有想过为什么要加上forName这句话呢?没有任何的输出呀,要它干什么用呢?事实上非常有用,我们看一下Driver的源码:

public class Driver extends NonRegisteringDriver  implements java.sql.Driver{ //构造函数  public Driver()    throws SQLException  {  }  //静态代码块  static   {    try    {      //把自己注册到DriverManager中      DriverManager.registerDriver(new Driver());    }    catch(SQLException E)    {      //异常处理      throw new RuntimeException("Can't register driver!");    }  }}

  该程序的逻辑是这样的:数据库驱动程序已经由NonRegisteringDriver实现了,Driver类只是负责把自己注册到DriverManager中。当程序动态加载该驱动时,也就是执行到Class.forName("com.mysql..jdbc.Driver")时,Driver类会被加载到内存中,于是static代码块开始执行,也就是把自己注册到DriverManager中。

  需要说明的是,forName只是把一个类加载到内存中,并不保证由此产生一个实例对象,也不会执行任何方法,之所以会初始化static代码,那是由类加载机制所决定的,而不是forName方法决定的。也就是说,如果没有static属性或static代码块,forName就是加载类,没有任何的执行行为。

  注意:forName只是加载类,并不执行任何代码。

建议105:动态加载不适合数组

 上一个建议解释了为什么要用forName,本建议就来说说那些地方不适合动态加载。如果forName要加载一个类,那它首先必须是一个类___8个基本类型排除在外,它们不是一个具体的类;其次,它必须具有可追溯的类路径,否则就会报ClassNotFoundException。

 在Java中,数组是一个非常特殊的类,虽然它是一个类,但没有定义类类路径,例如这样的代码:

public static void main(String[] args) throws ClassNotFoundException {    String [] strs = new String[10];    Class.forName("java.lang.String[]");  }

  String []是一个类型声明,它作为forName的参数应该也是可行的吧!但是非常遗憾,其运行结果如下: 

Exception in thread "main" java.lang.ClassNotFoundException: java/lang/String[]  at java.lang.Class.forName0(Native Method)  at java.lang.Class.forName(Class.java:186)

  产生ClassNotFoundException异常的原因是数组算是一个类,在声明时可以定义为String[],但编译器编译后为不同的数组类型生成不同的类,具体如下表所示:

数组编译对应关系表
元素类型编译后的类型
byte[][B
char[][C
Double[][D
Float[][F
Int[][I
Long[][J
Short[][S
Boolean[][Z
引用类型(如String[])[L引用类型(如:[Ljava.lang.String;)

  在编码期,我们可以声明一个变量为String[],但是经过编译后就成为了[Ljava.lang.String。明白了这一点,再根据以上的表格可知,动态加载一个对象数组只要加载编译后的数组对象就可以了,代码如下:

    //加载一个数组    Class.forName("[Ljava.lang.String;");    //加载一个Long数组    Class.forName("[J");

  虽然以上代码可以加载一个数组类,但这是没有任何意义的,因为它不能产生一个数组对象,也就是说以上代码只是把一个String类型的数组类和Long类型的数组类加载到了内存中(如果内存中没有改类的话),并不能通过newInstance方法生成一个实例对象,因为它没有定义数组的长度,在Java中数组是定长的,没有长度的数组是不允许存在的。

  既然反射不能定义一个数组,那问题就来了:如何动态加载一个数组呢?比如依据输入动态生成一个数组。其实可以使用Array数组反射类动态加载,代码如下:

    // 动态创建数组    String[] strs = (String[]) Array.newInstance(String.class, 8);    // 创建一个多维数组    int[][] ints = (int[][]) Array.newInstance(int.class, 2, 3);

  因为数组比较特殊,要想动态创建和访问数组,基本的反射是无法实现的,“上帝对你关闭一扇门,同时会为你打开一扇窗。”,于是Java就专门定义了一个Array数组反射工具类来实现动态探知数组的功能。

  注意:通过反射操作数组使用Array类,不要采用通用的反射处理API。




深圳二日游攻略大全深圳好玩的旅游景点深圳及周边旅游景点深圳两日游景点报价深圳旅游线路报价优惠那琴半岛地质海洋公园介绍?台山海洋公园简介? 从化3月赏花去哪玩?流溪河公园有桃花开吗? 漫步同里 一个人一座城(组图) 2015广州赏花什么地方好?流溪河公园3月有什么花开? 中山逍遥谷度假村团购价格?中山逍遥谷森林公园多少钱? 中山人民医院到逍遥谷怎样乘车?中山人民医院到逍遥谷乘车路线? 中山市五桂山逍遥谷怎么走?中山逍遥谷森林公园攻略? 中山逍遥谷度假村住宿价格?中山逍遥谷森林公园住宿预订? 蓝田瑶族风情园门票包含什么?龙门蓝田瑶族风情园有表演吗? 蓝田瑶族风情园团购价格?龙门蓝田瑶族风情园门票优惠政策? 广宁竹海自驾游团购价格?广宁竹海大观门票网上预订? 去三亚哪里好玩? 古今来趣味蜡像馆几点开门?三亚古今来趣味蜡像馆营业时间? 三亚西岛大学生门票多少钱?西岛大学生有优惠吗? 2015国庆节放假安排 2015年国庆节高速公路免费时间 三亚西岛学生有优惠吗?西岛门票学生优惠政策? Q65110A8879 Datasheet Q65110A8879 Datasheet Q65110A8888 Datasheet Q65110A8888 Datasheet Q65110A8908 Datasheet Q65110A8908 Datasheet 厦门园博园图片 厦门园博园图片 厦门园博园图片 去香港旅游买什么好 去香港旅游买什么好 去香港旅游买什么好 韶关百丈崖漂流 韶关百丈崖漂流 韶关百丈崖漂流