124

【java】java反射初探 ——“当类也学会照镜子” - 外婆的彭湖湾

 6 years ago
source link: http://www.cnblogs.com/penghuwan/p/7580145.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

正文

反射的作用

开门见山地说说反射的作用
1.为我们提供了全面的分析类信息的能力
2.动态加载
我理解的“反射”的意义
(仅个人理解哈)
我理解的java反射机制就是: 提供一套完善而强大的API“反射“类的结构。
打个比方,反射机制就像是一面镜子,而类就像是一个在照着镜子的人。
镜子(反射机制)照出(反射)了人的全貌(类的全方位的信息,例如方法,成员变量和构造器等的相关信息)
为什么要照镜子? 因为不照镜子看不清楚自己的全貌,“镜子”就是为了解决这个问题出现的为我们提供全面分析类的能力
好吧,我知道这听起来还是很模糊,让我们一步一步来

类也是对象

在java里有一句话:万物皆对象, 即使是int等基本类型,虽然本质上不是对象,但行为却也和对象密切相关(基本包装类型和自动装箱)
所以有一个可能完全打破我们常规思维的论断是: 类也是对象

“类”对象和“类”类型

好吧,其实说“ 类也是对象”并不太好应该说,java中每个类都有一个与之对应的“类”对象(Class对象),这个“类”对象由jvm生成,并保存了对应类的相关信息。例如,假设我们的java文件涉及三个类:a类,b类和c类,那么编译的时候就会对应生成a类的“类”对象,a类的“类”对象,a类的“类”对象,分别用于保存和a,b,c类对应的信息
我们的思维是这样的: 一个对象必然有一个与之对应的类,因为只有类才能实例化对象啊
那么,“类对象”的“上面”,应该还有一个类才对!这个“类之上的类”,就是java.lang.Class,它是所有“类”对象的类(这样说可能听起来很拗口)
我们这样声明一个“类”对象,假设这个类对象(Class对象)是a——
Class a
我们称a属于“类”类型(Class类型)
所以我们可以其实可以将java中的对象分为两种
1. 实例对象
2. Class对象
所以我们今天要讲的第一个内容是:  有别于平时使用的实例对象的——Class对象

取得Class对象的三种方式

我们假设有这么一个类叫MyClass:
public class MyClass {  }
那么取得该类对应Class对象的方法有三种:
一. 通过“类名.class”的方式取得
Class classInstance= MyClass.class;

二. 通过类创建的实例对象的getClass方法取得

MyClass myClass = new MyClass();
Class classInstance = myClass.getClass();

三.通过Class类的静态方法forName方法取得参数是带包名的完整的类名

Class classInstance = Class.forName("mypackage.MyClass");
【注意】
1.运行forName时候可能会因为找不到包名而抛出已检查异常ClassNotFoundException,所以我们需要将其包裹在try-catch语句中:
try {
  Class classInstance = Class.forName("mypackage.MyClass");
} catch (ClassNotFoundException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}
2.上面三种方法取得的对象都是相同的,所以效果上等价

利用反射API全面分析类的信息——方法,成员变量,构造器

我们上面提到,反射的一大作用是用于分析类的结构,或者说用于分析和这个类有关的所有信息。而这些信息就是类的基本的组成: 方法,成员变量和构造器
事实上,和我们上面所介绍的Class类和Class对象相似的是,一个类中的方法,成员变量和构造器也分别对应着一个对象
1.每个方法都对应有一个保存和该方法有关信息的Method对象, 这个对象所属的类是java.lang.reflect.Method;
2.每个成员变量都对应有一个保存和该变量有关信息的Field对象,这个对象所属的类是 java.lang.reflect.Field
3. 每个构造器都对应有一个保存和该构造器有关信息的Constructor对象,这个对象所属的类是java.lang.reflect.Constructor
方法,成员变量和构造器是附属于某一个类的,正因如此,我们应该先取得某一个类对应的Class对象,其次才考虑如何取得 Method/Field/Constructor对象
我们可以通过一系列的方法,从一个类的Class对象中取得对应的Method对象,Field对象和Constructor对象
假设c是一个类的Class对象:
通过 c.getDeclaredMethods()可取得这个类中所有声明方法对应的Method对象组成的数组
通过 c.getDeclaredFields()可取得这个类中所有声明的成员变量对应的Field对象组成的数组
通过 c.getConstructors(); 可取得这个类中所有构造函数所对应的Constructor对象所组成的数组
在下面的示例中,我们将遍历某一个类中方法,成员变量和构造器的名称:
MyClass.java:
public class MyClass {
  private int value; //成员变量
  public MyClass (int value) { this.value = value;   } //构造函数
  public int getValue() {  return value; } //方法1
  public void setValue(int value) {  this.value = value;  } //方法2
}
Test.java:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
 
public class Test {
  public static void printClassMessage (Object obj) {
    Class c = obj.getClass(); // 获取obj所属类的Class对象
Method [] methods = c.getDeclaredMethods(); // 获取方法对象列表
System.out.println("遍历MyClass类里的所有方法的名称:"); for(int i =0; i<methods.length; i++) { System.out.println(methods[i].getName()); }
Field [] fields = c.getDeclaredFields();   // 获取成员变量对象列表 System.out.println("遍历MyClass类里的所有成员变量的名称:"); for(int i =0; i<fields.length; i++) { System.out.println(fields[i].getName()); }   Constructor [] constructors = c.getConstructors();  // 获取构造函数对象列表 System.out.println("遍历MyClass类里的所有构造函数的名称:"); for(int i =0; i<constructors.length; i++) { System.out.println(constructors[i].getName()); } }    

public static void main(String [] args) {     MyClass myClass = new MyClass(1); // 创建一个MyClass对象     printClassMessage(myClass);  // 打印这个对象所属类的相关信息   } }
运行结果:
遍历MyClass类里的所有方法的名称:
getValue
setValue
遍历MyClass类里的所有成员变量的名称:
value
遍历MyClass类里的所有构造函数的名称:
mypackage.MyClass

上面的例子仅仅是作为一个展示,Method/Field/Constructor对象的API当然不仅限于getName这样获取名称的简单操作,所以接下来我将分别介绍更具体的反射API

利用反射API分析类中方法信息

getMethods和getDeclaredMethods方法

getMethods和getDeclaredMethods的区别在于:
getMethods取得的method对应的方法包括从父类中继承的那一部分,而
getDeclaredMethods取得的method对应的方法不包括从父类中继承的那一部分
例如上面通过打印getDeclaredMethods打印的MyClass的方法信息:
getValue
setValue
 让我们看看通过getMethods打印又会取得什么结果:
Test.java:
import java.lang.reflect.Method;
 
public class Test {
  public static void printMethodsMessage (Object obj) {
    Class c = obj.getClass();
    Method [] methods = c.getMethods();
    for (Method method : methods) {
      System.out.println(method.getName());
    }
  }
    
public static void main(String [] args) {     MyClass myClass = new MyClass(1);     printMethodsMessage(myClass);   } }
运行结果:
getValue
setValue
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
让我们思考一下: 一个方法有哪些“信息”值得(或需要)我们去分析呢?
主要是两部分:
1. 返回值
2. 方法参数
你可能猜的出来, Method对象已经提供了一套API去获取这些信息了,让我们来看看:

通过method.getReturnType()获取方法返回值对应的Class对象

import java.lang.reflect.Method;
 
public class Test {
  public static void printMethodsMessage (Object obj) {
    Class c = obj.getClass();  // 取得obj所属类对应的Class对象
    Method [] methods = c.getDeclaredMethods(); // 取得obj所属类中方法对应的Method对象组成的数组
    for (Method method : methods) { // 遍历Method对象
      String name = method.getName();   // 取得方法名
      Class returnClass = method.getReturnType(); // 获取方法返回值对应的Class对象
      String returnName = returnClass.getName();  //获取返回值所属类的类名——也即返回值类型
      System.out.println(name + "方法的返回值类型是" + returnName);
    }
  }
  public static void main(String [] args) {
    MyClass myClass = new MyClass(1);
    printMethodsMessage(myClass);
  }
}
运行结果:
getValue方法的返回值类型是int
setValue方法的返回值类型是void
通过method.getReturnType(),我们取得了该方法的返回值对应的Class对象(哈哈,绕了一圈后还是回到Class对象上了)
然后通过Class对象调用getName方法就取得了返回值所属的类的名称,也即返回值类型

通过method.getParameterTypes()获取方法各参数的Class对象组成的数组

MyClass.java:
public class MyClass {
  public void method1 (int a, long b) {   };
  public void method2 (float a, double b) {   };
  public void method3 (String str) {   };
}
Test.java:
public class Test {
  public static void printMethodsMessage (Object obj) {
    Class c = obj.getClass();  // 取得obj所属类对应的Class对象
    Method [] methods = c.getDeclaredMethods(); // 取得obj所属类中方法对应的Method对象组成的数组
    for (Method method : methods) { // 遍历Method对象
      String methodName = method.getName();   // 取得方法名
      String paramsStr = "";  // 用于存放某个方法参数类型列表的字符串
      Class [] paramsClasses = method.getParameterTypes();
      for (Class pc: paramsClasses) {
        String paramStr = pc.getName(); // 获取当前参数类型
        paramsStr+=paramStr + "  ";
      }
      System.out.println(methodName+ "方法的所有参数的类型列表:" + paramsStr);
    }
  }
  public static void main(String [] args) {     MyClass myClass = new MyClass();     printMethodsMessage(myClass);   } }
运行结果:
method2方法的参数类型列表:float  double 
method1方法的参数类型列表:int  long 
method3方法的参数类型列表:java.lang.String 

利用反射API分析类中成员变量信息

获取成员变量类型对应的的Class对象

读取成员变量的值
MyClass.java:
public class MyClass {
  private int number = 123;
  private String name ="彭湖湾";
}
Test.java:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
 
public class Test {
  public static void printFieldsMessage (Object obj) {
    Class c = obj.getClass();  // 取得obj所属类对应的Class对象
    try {
      Field field = c.getDeclaredField("name");  // 取得名称为name的field对象
      field.setAccessible(true); // 这一步很重要!!!设置为true才能访问私有成员变量name的值!
      String nameValue = (String) field.get(obj); // 获取obj中name成员变量的值
      System.out.println("MyClass类中name成员变量的值为:" + nameValue);  // 输出
    } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
       // TODO Auto-generated catch block
        e.printStackTrace();
    }
  }
    
public static void main(String [] args) {   MyClass myClass = new MyClass();     printFieldsMessage(myClass);   }
}
运行结果:
MyClass类中name成员变量的值为:彭湖湾

 通过getType方法读取成员变量类型的Class对象

这里只展现比较关键的两段代码:
Field field = class1.getDeclaredField(number");
System.out.print(field.getType().getName());
运行结果:
int
【注意】:因为java权限的原因,直接读取私有成员变量的值是非法的(加了field.setAccessible(true)后就可以了),但仍可以直接读取私有成员变量的类型

利用反射API分析类中构造器信息

分析构造函数的时候,其实思路大体上和分析方法时候一致,关键在于获取参数所属类的Class对象
区别在于:
1. 获取该类声明的构造器用的是getDeclaredConstructors方法而不是getDeclaredMethods方法
2. 构造函数没有返回值,所以不需要分析返回值(我似乎说了废话....)
废话不多说了,看下面的例子:
MyClass.java:
public class MyClass {
  public MyClass(int a, String str){}
}
Test.java:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
 
public class Test {
  public static void printContructorsMessage (Object obj) {
    Class c = obj.getClass();  // 取得obj所属类对应的Class对象
    Constructor [] constructors = c.getDeclaredConstructors();
    for (Constructor constructor : constructors) {
      Class [] paramsClasses =  constructor.getParameterTypes();
      String paramsStr = "";
      for (Class pc : paramsClasses) {
        String paramStr = pc.getName();
        paramsStr+=paramStr + "  ";
      }
      System.out.println("构造函数的所有参数的类型列表:" + paramsStr);
    }
  }
  public static void main(String [] args) {
    MyClass myClass = new MyClass(1, "彭湖湾");
    printContructorsMessage(myClass);
  }
}
运行结果:
构造函数的所有参数的类型列表:int  java.lang.String 

利用反射动态加载类,并用该类创建实例对象

动态加载类
我们用普通的方式使用一个类的时候,类是静态加载
而使用Class.forName("XXX")这种方式,则属于动态加载一个类
静态加载的类在编译的时候就能确定该类是否存在,但动态加载一个类的时候却无法在编译阶段确定是否存在该类,而是在运行时候才能够确定是否有这个类,所以要捕捉可能发生的异常
我们可以从eclipse的使用上有个相对直观的了解:
eclipse在保存的时候是可以自动编译的,SO
例如我们如果直接使用一个本就不存在的类NotExistClass的时候
保存后,在编译阶段就能够发现:“诶? 好像没有这个类哦!”
报的错误是:
NotExistClass cannot be resolved to a type
但是如果我们用Class.forName("XXX")动态加载一个类呢?
我们发现,保存后,在编译阶段已经不能发现这个错误了,对应的是要捕捉可能发生的异常
用该动态加载的类创建实例对象
Class对象有一个newInstance方法,我们可以用它来创建实例对象
Class classInstance = Class.forName("mypackage.MyClass");
MyClass myClass = (MyClass) classInstance.newInstance();
不过要注意的是,因为newInstance返回的是一个Object,所以要做强制类型转换,将其变成MyClass类型
捕捉可能产生的异常后:
public class Test {
   public static void main(String [] args) {
     try {
       Class classInstance = Class.forName("mypackage.MyClass");
       MyClass myClass = (MyClass) classInstance.newInstance();
     } catch (ClassNotFoundException e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
     } catch (InstantiationException e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
     } catch (IllegalAccessException e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
     }
   }
}
全文的总结
如果您已经看到这里了,那么请允许我在用多那么一点罗嗦的文字做个总结:
总结
1.反射为我们提供了全面的分析类信息的能力,例如类的方法,成员变量和构造器等的相关信息,反射能够让我们很方便的获取这些信息, 而实现这个获取过程的关键是取得类的Class对象,然后根据Class对象取得相应的Method对象,Field对象和Constructor对象,再分别根据各自的API取得信息。
2.反射还为我们提供动态加载类的能力
一些细节
1. API中getDeclaredXXX和getXXX的区别在于前者只获取本类声明的XXX(如成员变量或方法),而不获取超类中继承的XXX, 后者相反
2. API中, getXXXs(注意后面的s)返回的是一个数组, 而对应的 getXXX("键")按键获取一个值(这个时候因为可能报已检查异常所以要用try-catch语句包裹)
3. 私有成员变量是不能直接获取到值的!因为java本身的保护机制,允许你取得私有成员变量的类型,但是不允许直接获取值,所以要对对应的field对象调用field.setAccessible(true) 放开权限
最后
反射的API其实还有一堆...但是抱歉的是没办法一一给大家介绍,只能当作个“引子”给大家尝个鲜,另外,本人java小白,有诸多不当之处,还望大家指出,谢谢大家。
 
参考资料:
《java核心技术 卷1》—— Cay S. Horstmann, Gary Cornell

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK