JAVA反射机制详解

1.理解反射的基础:Class类

众所周知Java有个Object 类,是所有Java 类的继承根源,其内声明了数个应该在所有Java 类中被改写的方法:hashCode()、equals()、clone()、toString()、getClass()等。其中getClass()返回一个Class 对象。

Class 类十分特殊。它和一般类一样继承自Object,其实体用以表达Java程序运行时的classes和interfaces,也用来表达enum、array、primitive Java types(boolean, byte, char, short, int, long, float, double)以及关键词void。当一个class被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM 便自动产生一个Class 对象。如果您想借由“修改Java标准库源码”来观察Class 对象的实际生成时机(例如在Class的constructor内添加一个println()),这样是行不通的!因为Class并没有public constructor。

Class是Reflection故事起源。针对任何您想探勘的类,唯有先为它产生一个Class 对象,接下来才能经由后者唤起为数十多个的Reflection APIs。这些APIs将在稍后的探险活动中一一亮相。

Java类用于描述一类事物的共性,该类事物有什么属性,至于这个类的属性值是什么,则是由这个类的对象来确定,不同的实例对象有不同的属性值。java程序中的各个类,他们也属于同一类事物,也可以用一个类来描述这类事物,这个类就是Class,要注意和小写的class关键字的区别。Class类描述那些方面的信息呢?类的名字,类的访问属性,类所属于的包名,字段名称的列表,方法名称的列表,等等,学习反射,首先要明白这个Class类。

对比提问1:众多的人用一个什么类表示?众多的java类用什么表示?
人-》Person
Java类-》Class
此时很明显的体现了java面向对象的思想(一切皆为对象)。

对比提问2:Person类代表人,他的实例对象就是张三,李四这样一个个具体的人,Class类代表java类,那么他的实例对象又分别对应什么呢?
对应的是各个类在内存中的字节码(一个类有且仅有一份),例如,Person类的字节码,ArrayList类的字节码等等。(字节码就是类被加载到jvm中内存中的Class类的实例,然后利用这个字节码复制一个个指定类的对象)。

一个类被类加载器加载到内存中,占用一片存储空间,这个空间里边的内容就是类的字节码,不同的类的字节码是不同的,所以他在内存中的内容是不同的,这一个个的空间分别用一个个对象来表示,这些对象显示具有相同的类型,这个类型是什么呢?就是 Class类。

三种获取Class对象的方法:
不能用Class c = new Class(),没有这个构造方法的,应该采取下边的三种方法:
1).  Person.class 类名.class的方式;
2).  Person p = new Person; p.getClass(); person类的实例getClass的方式;
3).  Class.forName(“类名(全路径)”); 这个如果在jvm内存中没有找到指定类的字节码,就先将指定类加载到jvm内存中,然后在返回指定的类的Class实例,如果jvm中存在就直接返回。

需要注意的是:一般实现反射的话都是使用第三种方式Class.forName(“”).因为这个参数可以写成变量传进来,此时就可以读取配置文件的字符串来动态的获取相关类的字节码实例。

      /**
       * 获取一个类的字节码对象的三种方法 同一个类,类实例(字节码对象)在jvm中有且只有一份
       * 
       * @throws ClassNotFoundException
       */
      @Test
      public void test1() throws ClassNotFoundException {
          String str = "abc";
          // 表示返回此对象运行时类的 Class对象
          Class<? extends String> cls1 = str.getClass();
  
          // 编译器通过String就可以确定返回String类型的Class对象。
          Class<String> cls2 = String.class;
  
          // 给编译器看的,编译器并不知道通过forName会返回什么类型的Class对象
          Class<?> cls3 = Class.forName("java.lang.String");
  
          System.out.println(cls1 == cls2); // true
          System.out.println(cls1 == cls3); // true 
      }

由输出结果可知,每个类不管使用三种方式中的哪种方式来获取Class对象都是一份,从而证明了内存中同一个类的Class对象有且只有一份。

9个预定义的class对象:八种基本数据类型和void关键字。

       /**
        * 预定义的Class对象
        */
       @Test
       public void test2() {
           Class<String> cls2 = String.class;
           // primitive是原始的意思,判定指定的 Class对象是否表示一个基本类型(原始类型)
           System.out.println(cls2.isPrimitive());
   
           // 判定指定的 Class 对象是否表示一个基本类型
           System.out.println(int.class.isPrimitive());
   
           // 基本类型和对应包装类Class对象不是同一个
           System.out.println(int.class == Integer.class); //false int和Integer包装类不是同一份字节码
   
           /*
            * Integer.TYPE代表的是integer包装的基本类的字节码
            * 根据文档:表示基本类型 int 的 Class 实例
            */
           System.out.println(int.class == Integer.TYPE);  //true
   
           //数组有对应的数组类(Array类)
           System.out.println(int[].class.isPrimitive()); //false
   
           // 是不是数组类class对象
           System.out.println(int[].class.isArray());
       }

2.反射的概念

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

JAVA反射(放射)机制:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。但是JAVA有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods.

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

反射就是把java类中的各个成分映射成相应的java类。

例如:一个java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等信息也用一个个的java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一些方法来获取其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,他们是Filed,Method,Constructor,Package等等。(可以查看Class类的api就可以了解相关的方法)。

一个类的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这才是要点。

反射在很多地方都能用到,比如Spring(一个开放源代码的设计层面框架),Struts(一个基于MVC设计模式的Web应用框架),JUnit(一个Java语言的单元测试框架)等一些框架中,反射是框架设计的灵魂

3.构造方法的反射

Constructor类代表某个类中的一个构造方法。

       /**
        * 构造方法的反射
        * @throws NoSuchMethodException
        * @throws SecurityException
        * @throws InstantiationException
        * @throws IllegalAccessException
        * @throws IllegalArgumentException
        * @throws InvocationTargetException
        */
       @Test
       public void test3() throws NoSuchMethodException, SecurityException,
               InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException {
           Class<String> clazz = String.class;
           //获取String类的所有构造方法
           Constructor<?>[] constructors = clazz.getConstructors();
   
           /*
            * 通过构造函数反射实例化对象
            */
           String str1 = new String(new StringBuffer("adc"));
           Constructor<String> constructor1 = String.class
                   .getConstructor(StringBuffer.class);
           String str2 = constructor1.newInstance(new StringBuffer("abc"));
   
           //使用默认的构造方法实例化对象,newInstance
           String str3 = String.class.newInstance();
       }

使用newInstance方法来实例化对象的情况,newInstance方法先得到相应类的默认的构造方法,然后使用该默认的构造方法来实例化对象。
通过查看源码知:java会用缓存机制来缓存默认的构造方法实例,由此可以看出用了缓存说明获取Constructor对象是一个很耗时的操作。

扩展:因为反射机制的存在,所以单例模式并不能完全保证单例对象的唯一性。如何保证单例防反射攻击,请看这篇: 单例模式--反射--防止序列化破坏单例模式

4.成员变量的反射

Filed类代表某个类中的一个成员变量,即某个类的字节码对象的字段对象,不和具体的类的实例对应。

  public class ReflectPoint {
      private Date birthday = new Date();
  
      private int x;
      public int y;
      public String str1;
      public String str2;
      public String str3;
  
      public ReflectPoint(int x, int y) {
          super();
          this.x = x;
          this.y = y;
      }
      
      public ReflectPoint(String str1, String str2, String str3) {
          super();
          this.str1 = str1;
          this.str2 = str2;
          this.str3 = str3;
      }
  
  
  
      @Override
      public int hashCode() {
          final int prime = 31;
          int result = 1;
          result = prime * result + x;
          result = prime * result + y;
          return result;
      }
  
      @Override
      public boolean equals(Object obj) {
          if (this == obj)
              return true;
          if (obj == null)
              return false;
          if (getClass() != obj.getClass())
              return false;
          final ReflectPoint other = (ReflectPoint) obj;
          if (x != other.x)
              return false;
          if (y != other.y)
              return false;
          return true;
      }
  
      @Override
      public String toString() {
          return str1 + ":" + str2 + ":" + str3;
      }
  
      public int getX() {
          return x;
      }
  
      public void setX(int x) {
          this.x = x;
      }
  
      public int getY() {
          return y;
      }
  
      public void setY(int y) {
          this.y = y;
      }
  
      public Date getBirthday() {
          return birthday;
      }
  
      public void setBirthday(Date birthday) {
          this.birthday = birthday;
      }
  } 
      /**
       * 成员变量的反射
       * @throws SecurityException
       * @throws NoSuchFieldException
       * @throws IllegalAccessException
       * @throws IllegalArgumentException
       */
      @Test
      public void test4() throws NoSuchFieldException, SecurityException,
              IllegalArgumentException, IllegalAccessException {
          ReflectPoint pt1 = new ReflectPoint(3, 5);
          Class<? extends ReflectPoint> clazz = pt1.getClass();
          /*
           * 获取指定的字段对象
           * fieldY的值是多少?是5,错!fieldY不是对象身上的变量,而是类上,要用它去取某个对象上对应的值
           */
          Field fieldY = clazz.getField("y"); 
          
          //获取pt1对象上的y字段的值
          System.out.println("变量Y的值:" + fieldY.getInt(pt1));
  
          /*
           * getField只能获取public和defalut修饰的字段,此时x是私有变量。
           */
          /*
           * Field fieldX = clazz.getField("x");
           * System.out.println(fieldX.getInt(pt1));
           */
  
          /*
           * getDeclaredFields可以获取类中所有的成员变量 包括私有的变量
           */
          Field[] fields = clazz.getDeclaredFields();
          System.out.println("成员变量个数:" + fields.length);
  
          /*
           * 获取私有变量x的值
           * 虽然获取到x了,但是没有权限访问其中的值,所以需要设置x字段对象为可以访问的。
           */
          Field fieldX = clazz.getDeclaredField("x");
          fieldX.setAccessible(true);
          System.out.println("变量X的值:" + fieldX.getInt(pt1));
      }
      /**
       * 成员变量反射的综合案例 将任意一个对象中的所有String类型的成员变量所对应的字段内容中的“b”改成”a”
       * @throws SecurityException
       * @throws NoSuchFieldException
       * @throws IllegalAccessException
       * @throws IllegalArgumentException
       */
      @Test
      public void test5() throws NoSuchFieldException, SecurityException,
              IllegalArgumentException, IllegalAccessException {
          ReflectPoint pt1 = new ReflectPoint("ball", "basketball", "itcast");
          System.out.println("修改前:");
          System.out.println(pt1.str1);
          System.out.println(pt1.str2);
          System.out.println(pt1.str3);
  
          Class<? extends ReflectPoint> clazz = pt1.getClass();
          // /Field fieldX = clazz.getDeclaredField("x");
          Field[] fields = clazz.getDeclaredFields();  //获取所有的字段 包括私有字段
          for (Field field : fields) {
              /*
               * 获取字段的类型(返回的是类型的字节码实例) 判断是否和String类的字节码相等就能判断是否是String类型的字段了。  
               * 因为字节码只有一个,所以使用 == 比使用equeals更能体现意义
               */
              Class<?> filedType = field.getType();
              if (filedType == String.class) {
                  field.setAccessible(true);
                  String oldValue = (String) field.get(pt1);
                  String newValue = oldValue.replace("b", "a");
                  field.set(pt1, newValue);  //重新将替换后的新值set到相关到对象中去。
              }
           }
  
           System.out.println("修改后:");
           System.out.println(pt1.str1);
           System.out.println(pt1.str2);
           System.out.println(pt1.str3);
      }

结论:
从上可以看出对于一些对象,可以使用反射将其中的值都改掉,Spring实例化里边的值和初始化对象的值都是如此实现的。

5.成员方法的反射

Method类代表某个类中的一个成员方法。

      /**
       * 成员方法的反射
       * @throws SecurityException
       * @throws NoSuchMethodException
       * @throws InvocationTargetException
       * @throws IllegalArgumentException
       * @throws IllegalAccessException
       */
      @Test
      public void test6() throws NoSuchMethodException, SecurityException,
              IllegalAccessException, IllegalArgumentException,
              InvocationTargetException {
          String str = "abc";
          //获取指定方法的Method类的实例
          Method method = String.class.getMethod("charAt", int.class);
          //调用指定实例的方法,并传参
          //如果是Method代表的是静态方法,那么第一参数为null
          System.out.println(method.invoke(str, 1));
      }

6.对接收数组参数的成员方法进行反射

问题:
用反射的方式执行某个类中的main方法,写一个程序,根据用户提供的类名,去执行该类的main方法。如果使用传统的方式执行main方法可以直接Test.main(new String[]{}); 但是不能动态指定类名(用户提供的),因为传统的方式都已经编译了不能再改了。

      /**
       * 对接收数组参数的成员方法进行反射 用反射的方式执行某个类中的main方法,写一个程序,根据用户提供的类名,去执行该类的main方法。
       * 
       * @throws ClassNotFoundException
       * @throws SecurityException
       * @throws NoSuchMethodException
       * @throws InvocationTargetException
       * @throws IllegalArgumentException
       * @throws IllegalAccessException
       */
      @Test
      public void test7() throws ClassNotFoundException, NoSuchMethodException,
              SecurityException, IllegalAccessException,
              IllegalArgumentException, InvocationTargetException {
  
          String mainStr = "mytest.TestArguments";
          Method mainMethod = Class.forName(mainStr).getMethod("main",
                  String[].class);
          mainMethod.invoke(null, new String[] { "a", "b", "c" }); 
      }
  /**
   * 测试反射main方法的类
   */
  class TestArguments {
      public static void main(String[] args) {
          for (String arg : args) {
              System.out.println(arg);
          }
      }
  }

上述代码报错:java.lang.IllegalArgumentException: wrong number of arguments

明显是参数个数错误,main方法确实需要一个参数,而我也传了一个参数,为什么会错呢?

因为在为invoke传递数组类型的参数的时候,按照jdk1.5的做法,整个数组是一个参数,而按照jdk1.4的语法,会将数组拆开,每个元素对应一个参数,所以当把一个字符串数组作为一个参数传递给invoke方法时,javac会按照哪种方法进行处理呢?jdk1.5肯定要兼容jdk1.4的做法,会先按照1.4的语法处理,即把数组拆开为每一个元素作为一个参数。所以如果按照上边的方式会出现异常。可以采用下边的方式:

      /**
       * 对接收数组参数的成员方法进行反射 用反射的方式执行某个类中的main方法,写一个程序,根据用户提供的类名,去执行该类的main方法。
       * @throws ClassNotFoundException
       * @throws SecurityException
       * @throws NoSuchMethodException
       * @throws InvocationTargetException
       * @throws IllegalArgumentException
       * @throws IllegalAccessException
       */
      @Test
      public void test7() throws ClassNotFoundException, NoSuchMethodException,
              SecurityException, IllegalAccessException,
              IllegalArgumentException, InvocationTargetException {
  
          String mainStr2 = "mytest.TestArguments";
          Method mainMethod2 = Class.forName(mainStr2).getMethod("main",
                  String[].class);
  
          /*
           *  jdk1.4的做法
           *  重新包装字符串数组为一个object数组,此时按照1.4的方式拆开之后只有一个参数就是里边的string数组
           */
          mainMethod2
                  .invoke(null, new Object[] { new String[] { "a", "b", "c" } });
  
          /*
           * jdk1.5的做法
           * 重新包装字符串数组为一个object,此时给invoke方法传递的参数不是数组,编译器不会去拆解了,
           * (数组也是Object)
           */
          mainMethod2.invoke(null, (Object) new String[] { "a", "b", "c" });
      }

7.数组与Object的关系及数组的反射类型

根据文档中class的解释,对于数组的Class对象实例,具有相同维数(一维数组二维数组等)和元素类型的属于同一个类型,即具有相同的Class实例对象。中文文档的说明:所有具有相同元素类型和维数的数组都共享该 Class 对象。

     int[] a1 = new int[] { 1, 2, 3 };
     Integer[] a11 = new Integer[] { 1, 2, 3 };
     int[] a2 = new int[4];
     int[][] a3 = new int[2][3];
     String[] a4 = new String[] { "a", "b", "c" };
    
     System.out.println(a1.getClass() == a2.getClass()); // true

     // 都是一维数组,但是数组元素类型不一致,直接编译不通过。
     // System.out.println(a1.getClass() == a4.getClass());
     // 数组元素类型以致,但是维数不同,直接编译不通过。
     // System.out.println(a1.getClass() == a3.getClass());

数组的反射类型的name:

         /*
         * 基本类型的数组getName
         */
        //[I
        System.out.println("int[]:" + a1.getClass().getName());
        //int
        System.out.println("int:" + int.class.getName());

        /*
         * 引用类型的数组getName
         */
        // [Ljava.lang.String;
        System.out.println("String[]:" + a4.getClass().getName());
        // java.lang.String
        System.out.println("String:" + String.class.getName());

        /*
         * 与getName相对应的是forName 
         * 根据数组类型的Name类获取Class
         */
        // 基本类型
        Class<?> c1 = Class.forName("[I");
        System.out.println(c1.isArray()); // true

        // 引用类型
        Class<?> c2 = Class.forName("[Ljava.lang.String;");
        System.out.println(c2.isArray()); // true

对于"[I"中,[表示数组,I表示数组元素类型是int。
具体I代表什么参考getName方法的api注释:

基础类型:
Element Type  Encoding 
boolean        Z  
byte           B  
char           C   
double         D  
float          F  
int            I  
long           J  
short          S 

引用类型:
Element Type             Encoding
class or interface       Lclassname;  

例子: 
 String.class.getName()
 returns "java.lang.String"

 byte.class.getName()
 returns "byte"

 (new Object[3]).getClass().getName()
 returns "[Ljava.lang.Object;"

 (new int[3][4][5][6][7][8][9]).getClass().getName()
 returns "[[[[[[[I"

getSimpleName方法

  // String
  System.out.println(String.class.getSimpleName());

  // int[]
  System.out.println(a1.getClass().getSimpleName()); 

8.数组的反射应用

数组不是类,但是实现了Object中的方法。

按照Java语言规范的说法,Java数据类型分为两大类:基本数据类型和复合数据类型,其中复合数据类型包括数组、类和接口。

Array类(Array工具类)完成对数组的反射,是代表了对于数组类的描述。文档中说明:Array 类提供了动态创建和访问 Java 数组的方法。

扩展 : java.lang.reflect包

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容

  • 写在前面的话:很多人会说我直接new一个对象不就完了么,干嘛还用反射来获取对象。因为new属于静态编译,而反射属于...
    iDaniel阅读 8,606评论 1 4
  • 前言 Java反射机制很早的时候就有耳闻,期间也会去看看相关资料,但是又很快会忘记,所以,写一篇Blog来加深记忆...
    Android_Simon阅读 509评论 0 1
  • 活着就像个钟摆一会儿撞到无聊,一会儿又被无聊弹回去哐当撞到痛苦一边,来回在无聊与痛苦之间摆动,我时不常有轻生厌世的...
    单立人道长阅读 240评论 0 0
  • 自主招生又称自主选拔,主要选拔具有学科特长和创新潜质的优秀学生。是对现行统一高考招生按分数录取的一种补充,同时也是...
    咚咕隆咚阅读 239评论 0 0
  • 昨天承认了自己是个伪学习者以后,好像如释重负,面对真实地自己。似乎看清楚真实地自己才知道自己应该需要怎么做这件事。...
    一个景天阅读 407评论 0 1