zoukankan      html  css  js  c++  java
  • 理解反射的概念

    一、反射

    学员冯伟立(大二辍学,现广州电信)听完反射后的一句话:“反射就是把Java类中的各种成分映射成相应的java类”。这句话比许多书上讲的都透彻,都精辟!

    我们先学完这些反射API后,后面再通过一个综合案例来说明反射API的价值和作用。

     

    • 反射就是把Java类中的各种成分映射成相应的Java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。

     

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

     

    二、反射API

    2.1 Constructor类

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

      • 得到某个类所有的构造方法:

                       例子:Constructor constructors[]=Class.forName("java.lang.String").getConstructors();

      • 得到某一个构造方法:

                               例子:Constructor constructor=Class.forName("java.lang.String").getConstructor(StringBuffer.class);

      • 创建实例对象:
        • 通常方式:
          String s=new String(new StringBuffer("abc"));
        • 反射方式:

                                  Constructor constructor=Class.forName("java.lang.String").getConstructor(StringBuffer.class);
                                  String ss=(java.lang.String)constructor.newInstance(/*"abc"*/new StringBuffer("abc"));

    编译器只看代码的编译,不看代码的执行,我们开发人员一定要分清楚错误出现的不同阶段。

      • Class.newInstance()为创建一个对象提供了便利

        • 之前 class—constructor—>new obj

        • 现在提供了最简捷的方式: Class实例.newInstance()--默认调用无参构造

                           如:Class.forName(("java.lang.String").newInstance();

        • 该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。

     

     //java.lang.Class源码:
    public T newInstance() 
            throws InstantiationException, IllegalAccessException
        {
        if (System.getSecurityManager() != null) {
                checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader(), false);
        }
        return newInstance0();
        }
    
        private T newInstance0()
            throws InstantiationException, IllegalAccessException
        {
            // NOTE: the following code may not be strictly correct under
            // the current Java memory model.
    
            // Constructor lookup
            if (cachedConstructor == null) {//01.有缓存机制,说明得到某个类的构造器的字节码是比较消耗资源的
                if (this == Class.class) {
                    throw new IllegalAccessException(
                        "Can not call newInstance() on the Class for java.lang.Class"
                    );
                }
                try {
            Class[] empty = {};
                    final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
                    // Disable accessibility checks on the constructor
                    // since we have to do the security check here anyway
                    // (the stack depth is wrong for the Constructor's
                    // security check to work)
                    java.security.AccessController.doPrivileged
                        (new java.security.PrivilegedAction() {
                                public Object run() {
                                    c.setAccessible(true);
                                    return null;
                                }
                            });
                    cachedConstructor = c;//02.把字节码的构造器给缓存起来
                } catch (NoSuchMethodException e) {
                    throw new InstantiationException(getName());
                }
            }
            Constructor<T> tmpConstructor = cachedConstructor;//03.把缓存构造器交给临时变量
            // Security check (same as in java.lang.reflect.Constructor) 安全检查
            int modifiers = tmpConstructor.getModifiers();
            if (!Reflection.quickCheckMemberAccess(this, modifiers)) {
                Class caller = Reflection.getCallerClass(3);
                if (newInstanceCallerCache != caller) {
                    Reflection.ensureMemberAccess(caller, this, null, modifiers);
                    newInstanceCallerCache = caller;
                }
            }
            // Run constructor
            try {
                return tmpConstructor.newInstance((Object[])null);//04.调用构造器,返回该类实例
            } catch (InvocationTargetException e) {
                Unsafe.getUnsafe().throwException(e.getTargetException());
                // Not reached
                return null;
            }
        }
    //源码中之所以缓存构造器,是因为得到构造器的过程是比较消耗资源的。

     

     

     

    2.2 Field类

    • Field类代表某个类中的一个成员变量
    • 演示用eclipse自动身份Java类的构造方法
    • 问题:得到的Field对象时对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对相关联,那关联的是哪个对象呢?所以字段filedX代表的是x的定义,而不是具体的x变量。
    package com.itcast.day1;
    
    public class ReflectPoint {
    
         //x是私有的, 01.要使用 字节码.getDeclaredField("x") 获得x的定义fieldX.
    
         //02.使用fieldX.setAccessible(true) 来进行暴力破解private访问权限。 
        private int x;
    
       //y是共有的,用getField("y")就可以,也不涉及暴力破解权限。
    
        public int y;//共有的 
        
        public ReflectPoint(int x, int y) { 
            super(); 
            this.x = x; 
            this.y = y; 
        } 
    }

     

    /***main方法中的测试代码******成员变量的反射*********/ 
            ReflectPoint rp=new ReflectPoint(2, 4); 
            Field fieldY=rp.getClass().getDeclaredField("y"); 
            //fieldY 值是多少? 4吗?错! 
            //fieldY不是对象身上的变量,而是类上,要用它去取某个对象身上对应的值 
            System.out.println(fieldY.get(rp));//用fieldY去取对象rp上对应的变量y的值。 
            
    //     Field fieldX=rp.getClass().getField("x");//Exception in thread "main" java.lang.NoSuchFieldException: x 
            Field fieldX=rp.getClass().getDeclaredField("x"); 
            fieldX.setAccessible(true);//暴力反射!解决: can not access a member of class com.itcast.day1.ReflectPoint with modifiers "private" 
            System.out.println(fieldX.get(rp));

     

    • 成员变量反射的综合案例

      • 将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的”b”改成”a”。
    //javabean
    
    package com.itcast.day1;
    
    public class ReflectFieldEx { 
        private String firstName; 
        private int age; 
        private String address; 
        public ReflectFieldEx(String firstName, int age, String address) { 
            super(); 
            this.firstName = firstName; 
            this.age = age; 
            this.address = address; 
        } 
        @Override 
        public String toString() { 
            return "ReflectFieldEx [firstName=" + firstName + ", age=" + age 
                    + ", address=" + address + "]"; 
        } 
    }

     

    //利用反射来替换目标字符
    
    public static void change_a2b(Object obj )throws Exception{ 
            Field[] fields=obj.getClass().getDeclaredFields(); 
            for(int i=0;i<fields.length;i++){ 
                if(fields[i].getType()==String.class){//字节码在内存中只有一份,用==比较 ! 
                    fields[i].setAccessible(true); 
                    String fieldValue=fields[i].get(obj).toString(); 
                    if(fieldValue.contains("a")){ 
                        fieldValue=fieldValue.replace('a', 'b');//替换字符串中所有的a为b 
                        fields[i].set(obj,fieldValue); 
                    } 
                } 
            } 
        }
      • 测试代码:
    ReflectFieldEx rfe=new ReflectFieldEx("a1b2c3", 2, "library");  
    System.out.println(rfe); 
    change_a2b(rfe); 
    System.out.println(rfe);
      • 测试结果:
    ReflectFieldEx [firstName=a1b2c3, age=2, address=library] 
    ReflectFieldEx [firstName=b1b2c3, age=2, address=librbry]

     

    2.3 Method类

    • Method类代表某个类中的一个成员方法
    • 得到类中的某一个方法:
      • 例子:

      Method methodCharAt=Class.forName("java.lang.String").getMethod("charAt", int.class);

    • 调用方法:
      • 通常方式:System.out.println(s.charAt(1));
      • 反射方式:System.out.println(methodCharAt.invoke(s, 1));
        • 如果传递给Method对象的invoke()方法的一个参数为null,这有什么样的意义呢?说明该Mehtod对象对应的是一个静态方法!
    • jdk1.4和jdk1.5的invoke方法的区别:
      • jdk1.5, public Object invoke(Object obj,Object … args)
      • jdk1.4, public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素对应被调用方法的中各个参数。

     

    • 更深入的了解Method的调用-----------对接收数组参数的成员方法进行反射

                               --------用反射方法执行某个类中的main方法

      • 目标:
        • 写一个程序,这个程序能够根据用户提供的类名,去执行该类的main方法。
      • 问题:
        • 启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按照jdk1.5的语法,整个数组是一个参数,按照jdk1.4的语法,数组中的每个元素都对应一个参数,当把一个字符串作为参数传递给invoke方法时,javac会到底按照那种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按照jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码methodMain.invoke(null, new String[]{"xxxx"}),javac只把它当做jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。
      • 解决办法:
        • methodMain.invoke(null, new Object[]{new String[]{"xxxx"}})
        • methodMain.invoke(null, (Object)new String[]{"xxxx"}),编译时不把参数当作数组看待,也就不会把数组打散成若干个参数了。
      • 测试源代码:
        /***********成员方法的反射***********/ 
        Method methodCharAt=Class.forName("java.lang.String").getMethod("charAt", int.class); 
        System.out.println(s.charAt(1));//普通方式调用charAt方法 
        System.out.println(methodCharAt.invoke(s, 1));//反射方式,使用对象s,并传递参数1,调用methodCharAt代表字节码的方法。 
        System.out.println(methodCharAt.invoke(s, new Object[] { 2 }));//按照jdk1.4语法调用(还没有可变参数) 
         //main方法反射调用--右键—run as—configuration—arguements填写将要测试main方法所在的类名 
         Method methodMain=Class.forName(args[0]).getMethod("main", String[].class); 
        //methodMain.invoke(null,new String[]{"xxxx","yyy","zzz"});//01. 会按照jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数 
        methodMain.invoke(null,new Object[]{new String[]{"xxxx","yyy","zzz"}});//02. 依然按照jdk1.4语法,参数打散后发现,里面只有一个元素--Stirng[] 
        methodMain.invoke(null, (Object)new String[]{"xxxx","yyy","zzz"});//03.按照jdk1.5语法,参数不会被打散

    2.4 数组的反射

      • 有相同维度和元素类型的数组属于同一类型,即具有相同的Class实例对象。

      • 代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。

      • 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用,非基本类型的一维数组,即可以当做Object类型使用,又可以当作Object[]类型使用。

      • Arrays.asList()方法处理int[]和String[]时的差异。

      • Arrays工具类用于完成对数组的反射操作。

      • 测试代码:

          //数组的反射
                  int [] a1=new int[]{1,2,3};//一维基本类型数组
                  int [] a2=new int[4];
                  int [][] a3=new int[2][3];//二维基本类型数组 = 一维数组中的元素为一维数组  = {int[3],int[3]} = 一维引用类型数组
                  String[] s1=new String[]{"a","b","c"};//一维引用类型数组
                  System.out.println(a1.getClass()==a2.getClass());
                  
                  //打印数组--我们最想看到的是 每个元素被打印出来
                  System.out.println(a1);
                  System.out.println(s1);
                  
                  //基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用,
                  //非基本类型的一维数组,即可以当做Object类型使用,又可以当作Object[]类型使用。
                  //Arrays.asList()方法处理int[]和String[]时的差异
                  System.out.println(Arrays.asList(a1));//[[I@7b11a3ac]  int[3]-->Object
                  System.out.println(Arrays.asList(s1));//[a, b, c]      String[3]-->Object[]{"a","b","c"}-->List
                  //[[I@35d9dc39, [I@72093dcd]  int[2][3]-->int[]{int[3],int[3]}-->Object[]{int[3],int[3]}-->Object[]{Object,Object}
                  System.out.println(Arrays.asList(a3));

     

      • 利用数组的反射打印 代码:
          //利用数组的反射打印
          
          private static void printObject(Object obj) { 
              Class clazz=obj.getClass(); 
              if(clazz.isArray()){ 
                  int len=Array.getLength(obj); 
                  for(int i=0;i<len;i++){ 
                      System.out.println(Array.get(obj, i)); 
                  } 
              }else{ 
                  System.out.println(obj); 
              } 
          }
    //测试代码
    int [] a1=new int[]{1,2,3};
    
    String[] s1=new String[]{"a","b","c"};
    
    printObject(a1);//打印int[] 
    printObject(s1);//打印String[] 
    printObject("xxx");//打印字符串
    
    运行结果:
    
    1 
    2 
    3 
    a 
    b 
    c
          

     思考题:怎么得到数组中的元素类型?

                   没有办法,因为Object[]里可以放各种东西,我们只能得到某一个元素的类型,而不能得到Object[]的类型

               Object[] a=new Object(){1,flase,”abc”};

                a[0].getClass().getTYPE();//得到某一元素的类型

  • 相关阅读:
    Beginning Silverlight 4 in C#数据绑定和Silverlight List控件
    使用Socket通信实现Silverlight客户端实时数据的获取(模拟GPS数据,地图实时位置)
    分布式事物:第一章:分布式事物简介
    Redis数据结构存储系统:第三章:Redis在项目中如何使用?
    Android “NetworkOnMainThreadException”出错原因及解决办法
    asp.net 运行时, 报控件不存在
    Only the original thread that created a view hierarchy can touch its views
    android模拟器打开时比较慢,Run As就找不到模拟器
    db2数据库还原
    去掉代码中自动生成的TODO Autogenerated method stub
  • 原文地址:https://www.cnblogs.com/qq-757617012/p/4249540.html
Copyright © 2011-2022 走看看