zoukankan      html  css  js  c++  java
  • 初探Java反射机制

      反射库提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵java代码的程序库。这项功能被大量地应用于JavaBeans中。反射机制提供了在运行状态中获得和调用修改任何一个类的属性和方法的能力。

      Java反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。

      首先让我们来看一个简单的小程序,感性的认识一下Java反射机制:

     1 import java.lang.reflect.*;
     2 
     3 public class reflection {
     4     public static void main(String args[]) {
     5         try { 
     6             Class c = Class.forName("java.util.Stack"); 
     7             Method m[] = c.getDeclaredMethods(); 
     8                 
     9             for (int i = 0; i < m.length; i++) 
    10                 System.out.println(m[i].toString()); 
    11             } 
    12         catch (Throwable e){ 
    13             System.err.println(e); 
    14             } 
    15         } 
    16     }

    输出结果为:

    public synchronized java.lang.Object java.util.Stack.pop()
    public java.lang.Object java.util.Stack.push(java.lang.Object)
    public boolean java.util.Stack.empty()
    public synchronized java.lang.Object java.util.Stack.peek()
    public synchronized int java.util.Stack.search(java.lang.Object)

      上述代码通过Java反射机制列出了java.util.Stack 类的各方法名以及它们的限制符和返回类型。接下来,让我们详细分析一下Java反射机制的原理和使用。

    Class类

      要了解Java反射机制,首先必须得了解Class类。在程序运行期间,Java运行时系统始终为所有的对象维护一个被成为运行时的类型标识,这个信息跟踪着每个对象所属的类。虚拟机利用运行时信息选择相应的方法执行。保存这些信息的类被称为Class,这个名字很容易让人混淆。下面来看一个例子:

     1 package Reflect;
     2 
     3 /**
     4  * 通过一个对象获得完整的包名和类名
     5  * */
     6 class Demo{
     7     //other codes...
     8 }
     9  
    10 class hello{
    11     public static void main(String[] args) {
    12         Demo demo=new Demo();
    13         System.out.println(demo.getClass().getName());
    14     }
    15 }

      每个类中所包含的getClass()方法将会返回一个Class类的实例(getClass方法被定义在Object类中,Object类是所有类的祖先),最常用的Class方法是getName。这个方法将返回类的名字,如果类在某个包中,包的名字也作为类名的一部分返回。这也是获得Class对象的第一种方法。

    运行结果:Reflect.Demo

    还可以调用Class类的静态方法forName获得类名对应的Class对象:

    String className = "java.util.Date";
    Class cl = Class.forName(className);

      注意,className一定要包含包的路径。这是获得Class对象的第二种方法。

      这边再强调一下,Class类对象保存了每个对象所属类的信息,下面会讲到,通过Class类对象也可以创建一个相应类(Class保存信息的类)的实例,即某个对象。

      获得Class类对象的第三种方法很简单。如果T是任意的Java类型,T.class将代表匹配的对象。例如:

    Class cl1 = Date.class;
    Class cl2 = int.class;
    Class cl3 = Double[].class;

      一个Class对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如,int不是类,但int.class是一个Class类型的对象。

      通过newInstance()函数可以快速地创建一个类的实例:

     1 package Reflect;
     2  
     3 class Person{
     4     
     5     public String getName() {
     6         return name;
     7     }
     8     public void setName(String name) {
     9         this.name = name;
    10     }
    11     public int getAge() {
    12         return age;
    13     }
    14     public void setAge(int age) {
    15         this.age = age;
    16     }
    17     @Override
    18     public String toString(){
    19         return "["+this.name+"  "+this.age+"]";
    20     }
    21 
    22     private String name;
    23     private int age;
    24 }
    25  
    26 class hello{
    27     public static void main(String[] args) {
    28         Class<?> demo=null;
    29         try{
    30             demo=Class.forName("Reflect.Person");
    31         }catch (Exception e) {
    32             e.printStackTrace();
    33         }
    34         Person per=null;
    35         try {
    36             per=(Person)demo.newInstance();
    37         } catch (InstantiationException e) {
    38             e.printStackTrace();
    39         } catch (IllegalAccessException e) {
    40             e.printStackTrace();
    41         }
    42         per.setName("Rollen");
    43         per.setAge(20);
    44         System.out.println(per);
    45     }
    46 }
    运行结果:[Rollen  20]

      注意,newInstance方法调用默认的构造器初始化新创建的对象,如果这个类没有默认的构造器,就会抛出一个异常:

    java.lang.InstantiationException: Reflect.Person
        at java.lang.Class.newInstance0(Class.java:340)
        at java.lang.Class.newInstance(Class.java:308)
        at Reflect.hello.main(hello.java:39)
    Exception in thread "main" java.lang.NullPointerException
        at Reflect.hello.main(hello.java:47)

    利用反射分析类的能力

       在java.lang.reflect包中有三个类Field、Method和Constructor分别用于描述类的域、方法和构造器。这三个类都有一个叫做getName的方法,用来返回项目的名称。Field类有一个getType方法,用来返回描述域所属类型的Class对象。Method和Constructor类有能够报告参数类型的方法,Method类还有一个可以报告返回类型的方法。这三个类还有一个叫做getModifiers的方法,它将返回一个整数,用不同的位开关描述public和static这样的修饰符使用状况。

      Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的共有成员。Class类的getDeclareFields、getDeclareMethods和getDeclareConstructors方法将分别返回类中声明的全部域、方法和构造器,包括私有成员和受保护成员,但不包括超类的成员

       下面看一个通过Class调用其他类中的构造函数的例子:

     1 package Reflect;
     2  
     3 import java.lang.reflect.Constructor;
     4  
     5 class Person{     
     6     public Person() {       
     7     }
     8     public Person(String name){
     9         this.name=name;
    10     }
    11     public Person(int age){
    12         this.age=age;
    13     }
    14     public Person(String name, int age) {
    15         this.age=age;
    16         this.name=name;
    17     }
    18     public String getName() {
    19         return name;
    20     }
    21     public int getAge() {
    22         return age;
    23     }
    24     @Override
    25     public String toString(){
    26         return "["+this.name+"  "+this.age+"]";
    27     }
    28     private String name;
    29     private int age;
    30 }
    31  
    32 class hello{
    33     public static void main(String[] args) {
    34         Class<?> demo=null;
    35         try{
    36             demo=Class.forName("Reflect.Person");  //Demo中保存了Person类的信息,包括域、方法和构造器
    37         }catch (Exception e) {
    38             e.printStackTrace();
    39         }
    40         Person per1=null;
    41         Person per2=null;
    42         Person per3=null;
    43         Person per4=null;
    44         //取得全部的构造函数
    45         Constructor<?> cons[]=46         try{
    47             per1=(Person)cons[0].newInstance();
    48             per2=(Person)cons[1].newInstance("Rollen");
    49             per3=(Person)cons[2].newInstance(20);
    50             per4=(Person)cons[3].newInstance("Rollen",20);
    51         }catch(Exception e){
    52             e.printStackTrace();
    53         }
    54         System.out.println(per1);
    55         System.out.println(per2);
    56         System.out.println(per3);
    57         System.out.println(per4);
    58     }
    59 }
    View Code

    返回一个实现类的接口:

     1 package Reflect;
     2  
     3 interface China{
     4     public static final String name="Rollen";
     5     public static  int age=20;
     6     public void sayChina();
     7     public void sayHello(String name, int age);
     8 }
     9  
    10 class Person implements China{
    11     public Person() {
    12          
    13     }
    14     public Person(String sex){
    15         this.sex=sex;
    16     }
    17     public String getSex() {
    18         return sex;
    19     }
    20     public void setSex(String sex) {
    21         this.sex = sex;
    22     }
    23     @Override
    24     public void sayChina(){
    25         System.out.println("hello ,china");
    26     }
    27     @Override
    28     public void sayHello(String name, int age){
    29         System.out.println(name+"  "+age);
    30     }
    31     private String sex;
    32 }
    33  
    34 class hello{
    35     public static void main(String[] args) {
    36         Class<?> demo=null;
    37         try{
    38             demo=Class.forName("Reflect.Person");
    39         }catch (Exception e) {
    40             e.printStackTrace();
    41         }
    42         //保存所有的接口
    43         Class<?> intes[]=demo.getInterfaces();
    44         for (int i = 0; i < intes.length; i++) {
    45             System.out.println("实现的接口   "+intes[i].getName());
    46         }
    47     }
    48 }
    View Code

    运行结果:

    实现的接口   Reflect.China
    View Code

    取得其他类中的父类:

     1 class hello{
     2     public static void main(String[] args) {
     3         Class<?> demo=null;
     4         try{
     5             demo=Class.forName("Reflect.Person");
     6         }catch (Exception e) {
     7             e.printStackTrace();
     8         }
     9         //取得父类
    10         Class<?> temp=demo.getSuperclass();
    11         System.out.println("继承的父类为:   "+temp.getName());
    12     }
    13 }
    View Code

    运行结果:

    继承的父类为:   java.lang.Object
    View Code

       下面通过一个完整的例子来理解Field、Method和Constructor对象的具体使用。这个程序将提醒用户输入类名,然后输出类中所有的方法和构造器的签名,以及全部的域名。假如用户输入

      java.lang.Double

    程序将会输出:

     1 public final class java.lang.Double extends java.lang.Number
     2 {
     3    public java.lang.Double(double);
     4    public java.lang.Double(java.lang.String);
     5 
     6    public boolean equals(java.lang.Object);
     7    public static java.lang.String toString(double);
     8    public java.lang.String toString();
     9    public int hashCode();
    10    public static int hashCode(double);
    11    public static double min(double, double);
    12    public static double max(double, double);
    13    public static native long doubleToRawLongBits(double);
    14    public static long doubleToLongBits(double);
    15    public static native double longBitsToDouble(long);
    16    public volatile int compareTo(java.lang.Object);
    17    public int compareTo(java.lang.Double);
    18    public byte byteValue();
    19    public short shortValue();
    20    public int intValue();
    21    public long longValue();
    22    public float floatValue();
    23    public double doubleValue();
    24    public static java.lang.Double valueOf(java.lang.String);
    25    public static java.lang.Double valueOf(double);
    26    public static java.lang.String toHexString(double);
    27    public static int compare(double, double);
    28    public static boolean isNaN(double);
    29    public boolean isNaN();
    30    public static boolean isFinite(double);
    31    public static boolean isInfinite(double);
    32    public boolean isInfinite();
    33    public static double sum(double, double);
    34    public static double parseDouble(java.lang.String);
    35 
    36    public static final double POSITIVE_INFINITY;
    37    public static final double NEGATIVE_INFINITY;
    38    public static final double NaN;
    39    public static final double MAX_VALUE;
    40    public static final double MIN_NORMAL;
    41    public static final double MIN_VALUE;
    42    public static final int MAX_EXPONENT;
    43    public static final int MIN_EXPONENT;
    44    public static final int SIZE;
    45    public static final int BYTES;
    46    public static final java.lang.Class TYPE;
    47    private final double value;
    48    private static final long serialVersionUID;
    49 }
    View Code

    下面是源码:

      1 public class ReflectionTest
      2 {
      3    public static void main(String[] args)
      4    {
      5       // read class name from command line args or user input
      6       String name;
      7       if (args.length > 0) name = args[0];
      8       else
      9       {
     10          Scanner in = new Scanner(System.in);
     11          System.out.println("Enter class name (e.g. java.util.Date): ");
     12          name = in.next();
     13       }
     14 
     15       try
     16       {
     17          // print class name and superclass name (if != Object)
     18          Class cl = Class.forName(name);    
     19          Class supercl = 20          String modifiers = 21          if (modifiers.length() > 0) System.out.print(modifiers + " ");
     22          System.out.print("class " + name);
     23          if (supercl != null && supercl != Object.class) System.out.print(" extends "
     24                + supercl.getName());
     25 
     26          System.out.print("
    {
    ");
     27  28          System.out.println();
     29  30          System.out.println();
     31  32          System.out.println("}");
     33       }
     34       catch (ClassNotFoundException e)
     35       {
     36          e.printStackTrace();
     37       }
     38       System.exit(0);
     39    }
     40 
     41    /**
     42     * Prints all constructors of a class
     43     * @param cl a class
     44     */
     45    public static void printConstructors(Class cl)
     46    {
     47       Constructor[] constructors = 48    
     49       for (Constructor c : constructors)
     50       {
     51          String name = 52          System.out.print("   ");
     53          String modifiers = 54          if (modifiers.length() > 0) System.out.print(modifiers + " ");         
     55          System.out.print(name + "(");
     56 
     57          // print parameter types
     58          Class[] paramTypes = 59          for (int j = 0; j < paramTypes.length; j++)
     60          {
     61             if (j > 0) System.out.print(", ");
     62             System.out.print(paramTypes[j].getName());
     63          }
     64          System.out.println(");");
     65       }
     66    }
     67 
     68    /**
     69     * Prints all methods of a class
     70     * @param cl a class
     71     */
     72    public static void printMethods(Class cl)
     73    {
     74       Method[] methods = 75 
     76       for (Method m : methods)
     77       {
     78          Class retType = 79          String name = 80 
     81          System.out.print("   ");
     82          // print modifiers, return type and method name
     83          String modifiers = 84          if (modifiers.length() > 0) System.out.print(modifiers + " ");         
     85          System.out.print(retType.getName() + " " + name + "(");
     86 
     87          // print parameter types
     88          Class[] paramTypes = 89          for (int j = 0; j < paramTypes.length; j++)
     90          {
     91             if (j > 0) System.out.print(", ");
     92             System.out.print(paramTypes[j].getName());
     93          }
     94          System.out.println(");");
     95       }
     96    }
     97 
     98    /**
     99     * Prints all fields of a class
    100     * @param cl a class
    101     */
    102    public static void printFields(Class cl)
    103    {
    104       Field[] fields = cl.getDeclaredFields();
    105 
    106       for (Field f : fields)
    107       {
    108          Class type = f.getType();
    109          String name = f.getName();
    110          System.out.print("   ");
    111          String modifiers = Modifier.toString(f.getModifiers());
    112          if (modifiers.length() > 0) System.out.print(modifiers + " ");         
    113          System.out.println(type.getName() + " " + name + ";");
    114       }
    115    }
    116 }

    在运行时分析对象

      在编译程序时,如果知道想要查看的域名和类型,查看指定的域是一件很容易的事情,而利用反射机制可以查看在编译时还不清楚的对象域。我们知道,通过调用getDeclareFields方法可获得Field类对象,前面的程序中我们也可以知道,通过Field对象的getType和getName方法可以得到域的类型和名字,而通过get方法我们可以得到对应域的值。如果f是一个Field类型的对象,obj是某个包含f域的类的对象,f.get(obj)将返回一个对象,其值为obj的f域的当前值。同样,我们可以通过set函数在运行中设置某一个域的值。

     1 class hello {
     2     public static void main(String[] args) throws Exception {
     3         Class<?> demo = null;
     4         Object obj = null;
     5  
     6         demo = Class.forName("Reflect.Person");
     7         obj = demo.newInstance();
     8  
     9         Field field = demo.getDeclaredField("name");
    10         field.setAccessible(true);
    11         field.set(obj, "Jack");
    12         System.out.println(field.get(obj));
    13     }
    14 }

      这里注意的一点是,反射机制的默认行为是受限于Java的访问控制的,name是一个私有的域,所以get和set访问它将会抛出一个异常。如果一个java程序没有受到安全管理器的控制,就可以覆盖访问控制。为了达到这个目的,需要调用Field、Method和Constructor对象的setAccessible方法。

     使用反射编写泛型数组代码

       在实际使用中我们常常会用到Arrays类的copyOf方法实现扩展已经填满的数组(动态的创建数组)。

    Person[] a = new Person[100];
    ...
    //arrays is full
    a = Arrays.copyOf(a, 2*a.length);

      那么现在问题来了,我们不看源码,考虑如何实现这样一个通用的动态数组生成方法呢?我们首先可能会想到把Person[]数组转变为Object[]数组,So我们会这样实现:

    public static Object[] badCopyOf(Object[] a, int newLength) {
            Object[] newarray = new Object[newLength];
            System.arraycopy(a, 0, newarray, 0, newLength);
            return newarray;
        }

      然而,在实际使用得到的结果数组时会出现问题,因为返回的结果为Object[]类型,不能被转换成其他类型的数组。将一个Person[]临时转化成Object[]数组,然后再把它转换回来是可以的,但一个从开始就是Object[]的数组却永远不能转换成Person[]数组。So,我们需要知道原数组的类型才能创建与原数组类型相同的新数组。而在构建数组的过程中非常重要的一个函数是java.lang.reflect包中Array类中的静态方法newInstance,它能够构造新数组:

    Object newArray = Array.newInstance(componentType, newLength);

    这两个参数,第一个是数组元素的类型,第二个是数组的长度。我们可以通过Array.getLength(a)来得到数组长度。

    那么我们现在主要的任务是获得数组元素类型,一般需要进行以下工作:

    1)首先获得a数组的类对象。

    2)确认它是一个数组。

    3)使用Class类(只能定义表示数组的类对象)的getComponentType方法确定数组对应的类型。

    于是乎:

    public static Object goodCopyOf(Object[] a, int newLength) {
            Class cl = a.getClass();
            if(!cl.isArray())
                return null;
            Class componentType = cl.getComponentType();
            int length = Array.getLength(a);
            Object newArray = Array.newInstance(componentType, length);
            System.arraycopy(a, 0, newArray, 0, Math.min(newLength, length));
            return newArray;
        }

    简单测试:

    int[] a = {1, 2, 3, 4, 5};
    a = (int[]) goodCopyOf(a, 10);

     最后我们再附上jdk中copyOf的实现源码:

    public static <T> T[] copyOf(T[] original, int newLength) {
            return (T[]) copyOf(original, newLength, original.getClass());
        }
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
            @SuppressWarnings("unchecked")
            T[] copy = ((Object)newType == (Object)Object[].class)
                ? (T[]) new Object[newLength]
                : (T[]) Array.newInstance(newType.getComponentType(), newLength);
            System.arraycopy(original, 0, copy, 0,
                             Math.min(original.length, newLength));
            return copy;
        }

    参考资料:

    http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html

    《java核心技术 卷一》

  • 相关阅读:
    数据库中的大数据字段和二进制大数据字段(图片)
    mysql中的存储过程和事务隔离
    关于数据库的三个范式的详解
    mysql与java的之间的连接
    mysql中的第三范式
    如何设置ssh安全只允许用户从指定的IP登陆
    MySQL 出现 The table is full 的解决方法
    ssh "openssh-daemon is stopped"操作之伤+sftp访问“-bash: /dev/null: Permission denied”
    CentOS 6 用SVN自动提交文件到web服务器
    centos下pg_dump的服务器版本不匹配问题
  • 原文地址:https://www.cnblogs.com/big-xuyue/p/4021171.html
Copyright © 2011-2022 走看看