您的位置:首页 > 职场人生

黑马程序员--Java反射技术

2012-02-19 16:00 344 查看
---------------------- android培训java培训、期待与您交流! ------------------------------------------------------------------------------------------------------------------------------------------------

 

Java中的反射技术:

 

反射的基石:Class类

Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。

Class类代表Java类,它的各个实例对象对应各个类在内存中的字节码。

例如,Person类的字节码,ArrayList类的字节码,等等。

一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,

不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一

个个的对象来表示,这些对象显然具有相同的类型,这个类型就是Class。

获取各个字节码对应的实例对象:

主要有三种方式:

类名.class。                         例如,System.class

对象.getClass()。                 例如,new Date().getClass()

Class.forName("类名")。      例如,Class.forName("java.util.Date");

总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void…
 
 

1.Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这个

类的实例对象来确定的,不同的实例对象有不同的属性值。Java程序中的各个Java类,它们是否属于同一类事物

是不是可以用一个类来描述这类事物呢?这个类的名字就是Class,要注意与小写class关键字的区别哦。Class类

描述了哪些方面的信息呢?类的名字,类的访问属性,类所属于的包名,字段名称的列表、方法名称的列表,等等

。学习反射,首先就要明白Class这个类。写如下代码进行对比理解:

      /*

             Person p1 = new Person("zhangsan");

             Person p2 = new Person("lisi");

     */

        /*

             Class x1 = Vector类在内存里的字节码

             Class x2= Date类在内存里的字节码

       */

             Class x1 =
Vector.class;

             Class x2 =
Date.class;

             每个java类都是Class的一个实例对象,它们的内容不同,但是,它们的特征相同,譬如,都有方法,有字段,有父类,有包。

反射的定义及作用:

反射就是把Java类中的各种成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员

变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。

表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的

实例对象来表示,它们是Field、Method、Contructor、Package等等。

一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些

实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。

Constructor类:

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

得到某个类所有的构造方法:
例子:Constructor [] constructors=Class.forName("java.lang.String").getConstructors();
得到某一个构造方法:
例子:
     Constructorconstructor =Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
     //获得方法时要用到类型

     创建实例对象:
     通常方式:Stringstr = newString(newStringBuffer("abc"));
     反射方式:
Stringstr =(String)constructor.newInstance(newStringBuffer("abc"));
     //调用获得的方法时要用到上面相同类型的实例对象

     Class.newInstance()方法:
     例子:Stringobj =(String)Class.forName("java.lang.String").newInstance();
     该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
    该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实
12eb3
例对象。
 
 Field类:
 
Field类代表某个类中的一个成员变量

问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只

有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字

段fieldX 代表的是x的定义,而不是具体的x变量。

示例代码:

      public   class   ReflectPoint {

               private
int x;

               public
int y;

               public
ReflectPoint(int x,int y) {

                       super();

                       this.x = x;

                       this.y = y

             }

       }

    ReflectPoint    point  =   new   ReflectPoint(1,7);

    Field y = Class.forName("cn.itcast.corejava.ReflectPoint").getField("y");

    System.out.println(y.get(point));

     //这个会报错,因为x变量是私有的,getField只能获取权限修饰符为public的。

    //Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getField("x");     //获取声明的所有变量。

    Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getDeclaredField("x");

    x.setAccessible(true);//暴力访问。

    System.out.println(x.get(point));

我把自己的变量定义成private,就是不想让人家访问,可是,现在人家用暴力反射还是能够访问我,这说不

通啊,能不能让人家用暴力反射也访问不了我。首先,private主要是给javac编译器看的,希望在写程序的时

候,在源代码中不要访问我,是帮组程序员实现高内聚、低耦合的一种策略。你这个程序员不领情,非要去

访问,那我拦不住你,由你去吧。同样的道理,泛型集合在编译时可以帮助我们限定元素的内容,这是人家

提供的好处,而你非不想要这个好处,怎么办?绕过编译器,就可以往集合中存入另外类型了。

Method类:

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

得到类中的某一个方法:

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

       调用方法:

       通常方式:System.out.println(str.charAt(1));

       反射方式:System.out.println(charAt.invoke(str, 1));

       如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!

       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方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt

                       方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。

 Method类的Invoke方法的一点问题:

 通过反射方式来调用某个类的main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的

语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理

呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传

递参数时,不能使用代码mainMethod.invoke(null,newString[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5

的语法解释,因此会出现参数类型不对的问题。

解决办法:
1.mainMethod.invoke(null,newObject[]{new String[]{"xxx"}});
2.mainMethod.invoke(null,(Object)newString[]{"xxx"});,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了
 
示例代码:

    Class   clazz =   Class.forName(arg[0]);

            Method    mMain =   
clazz.getMethod("main",String[].class);

            mMain.invoke(null,newObject[]{newString[]{"aaa","bbb"}});

            mMain.invoke(null,(Object)newString[]{"aaa","bbb"});

            classTestArrayArguments {

                            publicstaticvoidmain(String []args)

                            {

                                          for(Stringarg:args)

                                         {

                                                  
System.out.println("----------"+arg +"----------");

                                         }

                           }

            }

数组的反射:

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

示例代码:

String[] a1 = new String[8];

String[] a2 = new String[9];

String[][] a3 = new String[6][4];

 

System.out.println(a3.getClass().getName());

System.out.println(a1.getClass()== a2.getClass());

System.out.println(a1.getClass()== a3.getClass());

 

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

基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既

可以当做Object类型使用,又可以当做Object[]类型使用。

 

Array工具类用于完成对数组的反射操作。
示例代码:
 
    private   static   void    printObject(Object obj) {

                if(obj.getClass().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);

                                   }

     }
 
类加载器加载文件以及对文件的管理:

 
示例代码:
 

/*

 * Eclipse的一个功能的介绍:Eclipse会将位于Src目录中的源文件(.java文件)编译为

 * .class文件,编译后的.class文件按照Src的目录结构组织自动存储在classpath目

 * 录中(也就是bin目录中)。而对于非.java文件,则原封不动的移动到classpath相应

 * 的目录中。其中bin(classpath目录)目录和Src目录是同级目录。

 * 未来学习的框架,用到的都是这种类加载器加载配置文件的方式设计的。配置文件都

 * 放置在classpath目录下。test1--test4只能实现的是对配置文件的内容的读取,而

 * test6既可以读取配置文件,也可以保存配置文件,但test6的关键在配置文件绝对路

 * 径的获取。

 */

    public class ResourceManager {

                          // 路径前边没有“/”,代表的都是相对路径。

                   @Test

                    public void test1() throws Exception {

                                    // 用类加载器加载配置文件的话,最初是相对于Classpath目录下,在硬盘上也就是bin目录,也可以

                                    // 看做是Eclipse的src目录。但这个不能使用“/”打头。

                                    // 如果是位于Classpath目录的子目录中,要指明目录。

                            InputStream ips = ResourceManager.class.getClassLoader()

                                                                                    .getResourceAsStream("day04/config1.properties");

                            Properties prop = new Properties();

                            prop.load(ips);

                            String name = prop.getProperty("Name");

                            System.out.println(name);

                  }

                @Test

                public void test2() throws Exception {

                                 // 也可以使用字节码自带的加载配置文件的方法,其实内部调用的还是classloader来加载.

                                 // 此时加载的时候,是相对于当前类所在的目录。例如ResourceManager在day04目录下

                                 // 则加载的时候,就从day04下开始找。

                          InputStream ips = ResourceManager.class

                                                                                  .getResourceAsStream("config1.properties");

                          Properties prop = new Properties();

                          prop.load(ips);

                          String name = prop.getProperty("Name");

                          System.out.println(name);

               }

              @Test

              public void test3() throws Exception {

                                             // 使用字节码自带的加载配置文件的方法,其实内部调用的还是classloader.

                                             // 此时加载的时候,是相对于当前类所在的目录。例如ResourceManager在day04目录下

                                             // 则加载的时候,就从day04下开始找。

                         InputStream ips = ResourceManager.class

                                                                                   .getResourceAsStream("res/config2.properties");

                         Properties prop = new Properties();

                         prop.load(ips);

                         String name = prop.getProperty("Name");

                         System.out.println(name);

               }

              @Test

              public void test4() throws Exception {

                                                       // 使用字节码自带的加载配置文件的方法,可以在路径最前边打“/”,该“/”代表的是

                                                       // classpath目录。

                        InputStream ips = ResourceManager.class

                                                                                 .getResourceAsStream("/day04/res/config2.properties");

                        Properties prop = new Properties();

                        prop.load(ips);

                         /*

                               * 此处对资源释放的理解:例如创建一个窗口程序,一个对象对应这个窗口,程序用这个对象来操纵

                               * 窗口。一种情况是,这个对象被当做是垃圾被GC回收了,但是这个系统资源--窗口还没有被释

                               * 放,还被占用着。对象是GC管理的,而系统资源是操作系统管理的。所以与此类似的就是IO流,IO流

                               * 调用close方法就是为了通知操作系统,收回那些系统资源,让其他程序调用。此时,并 不是为了让GC来回收IO对象。

                        */

                      ips.close();

                      String name = prop.getProperty("Name");

                      System.out.println(name);

          }

             @Test

              public void test5() throws Exception {

                                                       // 如过是用FileInputStream的话,如果用相对路径,则相对的是当前的项目

                                                       // 这个一般不用。

                                        InputStream ips = new FileInputStream("src/day04/config1.properties");

                                        Properties prop = new Properties();

                                        prop.load(ips);

                                        ips.close();

                                        String name = prop.getProperty("Name");

                                        System.out.println(name);

             }

               @Test

               public void test6() throws Exception {

                                                     // 这个是用FileInputStream通过绝对路径来加载配置文件的,但是这个绝对路径不是

                                                    // 硬编码的,是通过某种方式 计算得到(具体怎么得到,我也不清楚),这样知道了绝对

                                                    // 路径,就可以用FileOutputStream将修改后的配置内容写入到硬盘,实 现了配置文件

                                                    // 的读写功能。

                                         InputStream ips = new FileInputStream(

                                                             "F:/EclipseWorkspace3/JavaEnhance/bin/day04/config1.properties");

                                         Properties prop = new Properties();

                                         prop.load(ips);

                                         ips.close();

                                         String name = prop.getProperty("Name");

                                        System.out.println(name);

                               }

               }

反射的作用:
 
反射主要是用来实现框架功能的。
 
框架与框架要解决的核心问题:

什么是框架,例如,我们要写程序扫描.java文件中的注解,要解决哪些问题:读取每一样,在每一个中查找@,找到的

@再去查询一个列表,如果@后的内容出现在了列表中,就说明这是一个我能处理和想处理的注解,否则,就说明它不

是一个注解或者说至少不是一个我感兴趣和能处理的注解。接着就编写处理这个注解的相关代码。现在sun提供了一个

apt框架,它会完成所有前期工作,只需要我们提供能够处理的注解列表,以及处理这些注解的代码。Apt框找到我们感

兴趣的注解后通知或调用我们的处理代码去处理。

 

我在写框架时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象了,而要用反射方式来做。
 
JavaBen的内省操作:
 
 
什么是javabean:
 
JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合
某种命名规则。

如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为
值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一
些相应的方法来访问,大家觉得这些方法的名称叫什么好呢?JavaBean的属性是根据其中的setter和getter方法来确定的,
而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,至于你把它存到哪个变量上,用管吗?如果方
法名为getId,中文意思即为获取id,至于你从哪个变量上取,用管吗?去掉set前缀,剩余部分就是属性名,如果剩余部
分的第二个字母是小写的,则把剩余部分的首字母改成小的。

setId()的属性名id

isLast()的属性名last

setCPU的属性名是什么?CPU

getUPS的属性名是什么?UPS

总之,一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。
 
一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才
会去了解和应用JavaBean!好处如下:

在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,
那你就没什么挑选的余地!

JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的x,怎么
做,有一定难度吧?用内省这套api操作JavaBean比用普通类的方式更方便。

内省的例子:
 

     private static void setProperties(Object pt1, String propertyName,

                                                                                                     Object value) throws IntrospectionException,

                                                                                                                                        IllegalAccessException, InvocationTargetException {

                                          PropertyDescriptor pd2 = new PropertyDescriptor(propertyName,pt1.getClass());

                                          Method methodSetX = pd2.getWriteMethod();

                                          methodSetX.invoke(pt1,value);

    }

     private static Object getProperty(Object pt1, String propertyName)

                                                                                                   throws IntrospectionException, IllegalAccessException,

                                                                                                                                                                              InvocationTargetException {

                                        PropertyDescriptor pd = new PropertyDescriptor(propertyName,pt1.getClass());

                                        Method methodGetX = pd.getReadMethod();

                                        Object retVal = methodGetX.invoke(pt1);

                             }

                                       return retVal;

             }

    }

使用BeanUtils工具包来处理内省问题:

示例代码:

 

public class BeanUtilsTest {

        @Test

           public  void   test1() throws Exception {

           Point p = new Point(7, 8);

           String propertyName = "x";

           Object value = 9;

           BeanUtils.setProperty(p, propertyName, value);

           Object retVal = BeanUtils.getProperty(p, propertyName);

           // 注意Object的getClass()方法是final类型的方法,不能被修改;其次

           // 该方法返回的是运行时的对象所属的类。在本例中是多态的形式:变量

           // obj在运行的时候,指向的是一个String类型的对象,所以结果才是

           // java.lang.String.

         System.out.println(retVal.getClass().getName());

     }

                                                                 // BeanUtils设置某个对象的某个属性的值的时候,该值的类型是Object类型的

                                                                 // BeanUtils.setProperty(Object bean,String name,Object value)

                                                                 // 通过test3和test4,我们可以知道,BeanUtils工具包,传递的value值可以是

                                                                 // String类型的,返回的value值也是String类型的,这样做的好处就是:以后的

                                                                 // web程序,通过表单写入的是String类型的参数,传递到服务器端,然后要转型

                                                                 // 为相应的类型才可以赋值给某个对象的属性。而通过web表单呈现的数据,亦是

                                                                 // 转型后的结果。

        @Test

         public void test2() throws Exception {

              Point p = new Point(7, 8);

             String propertyName = "x";

             // 这个的话,因为X属性本来就是int类型的,所以不用多说。

             BeanUtils.setProperty(p, propertyName, 9);

             System.out.println(p.getX());

       }

       @Test

        public void test3() throws Exception {

             Point p = new Point(7, 8);

             String propertyName = "x";

                                                           // 但是这个,X属性的类型是int类型的,但是传递进去的值为String类型的value.

                                                           // 这说明了BeanUtils工具包对输入的value进行了转型。

            BeanUtils.setProperty(p, propertyName, "9");

            System.out.println(p.getX());

        } 

       @Test

       public void test4() throws Exception {

           Point p = new Point(7, 8);

          String propertyName = "x";

          // 获取对象p的x属性的值。

          System.out.println(BeanUtils.getProperty(p, propertyName));

          // 获取BeanUtils工具包返回的x属相值的类型。

          System.out.println((BeanUtils.getProperty(p, propertyName)).getClass()

                                                                                                                       .getName());

      }

 // BeanUtils工具包,支持属性的级联操作。从test5中可以看到,我们通常做

 // 的是把birthday当做Person对象的属性来为这个属性设置值或者获取值,但

 // 是此时,又把birthday当做是一个Date类型的对象,该对象有一个time属性.

 // 为该属性赋值。我想BeanUtils工具包内部的工作原理应该是,判断传入的属

 // 性名称是否为级联类型的.就本例而言:

 // 是级连类型的名称,就会递归调用setProperty(bean,name,value) 这个方

 // 法,传入的name,value是什么都很好确定,但要传入的bean是什么呢?就会从p对象

 // 中通过反射等方式得到getBirthday()方法,method.invoke(p),返回birthday属性

 // 来当做是一个bean。

 // 这个时候就要注意了,此时如果在Person类中birthday没有初始化,因为是

 // 引用类型的数据,所以method.invoke(p)就会返回一个null。

 // 然后将null传入到setPropetyName作为bean,运行后会报告:No bean specificed的

 // 错误。

 @Test

  public void test5() throws Exception {

                    Person p = new Person();

                    String propertyName = "birthday.time";

                    BeanUtils.setProperty(p, propertyName, "1111");

                    System.out.println(BeanUtils.getProperty(p, propertyName));

          }

   }

第二种方案:

public class PropertyUtilsTest {

       @Test

                                                      // BeanUtils通常是以字符串的形式来操作属性的,而PropertyUtils则

                                                      // 是以属性的 原始类型来操作的,这就是二者的区别。

       public   void    test1() throws Exception {

            Point p = new Point(7, 8);

            String propertyName = "x";

            Object value = 9;

            /*

             * Object value="9" 如果是这个,就会报错,

             */

           PropertyUtils.setProperty(p, propertyName, value);

           Object retVal = PropertyUtils.getProperty(p, propertyName);

           System.out.println(retVal);

           System.out.println(retVal.getClass().getName());

        }

   }

 

----------------------android培训java培训、期待与您交流! --------------------------------------------------------------------------------------------------------------------------------------------------

详细请查看:http://edu.csdn.net/heima
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息