Java反射基础总结

声明:本文仅仅是总结学习笔记,使用了以下2篇文章

1.  Java系列笔记JavaRTTI和反射机制

(http://wenku.baidu.com/link?url=9vDbJfeIXeDuBNcGad4jLWZ3otRJO6Nb3IIfIWC5_9IzaikRL5QOMmZFZz_jXf_NW-fRcApy58OHrzxSE1AovyqJl7pBWvlYCzaV3mC1SdG)

2.  张孝祥_Java_基础加强_高新技术笔记部分内容

 

一.   前言

 

并不是所有的 Class 都能在编译时明确,因此在某些情况下需要在运行时再发现和确定类型信息(比如:基于构建编程,),这就是 RTTI(Runtime TypeInformation,运行时类型信息)。

运行时类型识别(RTTI,Run-Time Type Identification)是Java中非常有用的机制,在Java运行时,RTTI维护类的相关信息。多态(polymorphism)是基于RTTI实现的。RTTI的功能主要是由Class类实现的。

在 java 中,有两种 RTTI 的方式,一种是传统的,即 假设在编译时已经知道了所有的类型;还有一种,是利用 反射机制,在运行时再尝试确定类型信息。

本文主要讲反射方式实现的RTTI,建议在阅读本文之前,先了解类的加载机制

在本文中,将共同使用下面的红苹果类 RedApple,该类中定义了公有、私有方法,变量,构造方法,父类、父接口等:

 

package com.xmm.trri;

 

/**

 * 接口:水果

 */

public interfaceIFruit {

 

    /**

     * 方法:成长

     * @param  sun 太阳

     * @return 成长信息

     * @throws  Exception

     */

    String grow(String sun)throws Exception;

}

 

/**

 * 抽象类:苹果

 * 继承:水果

 */

public abstract class AbstractApple implements IFruit {

 

    /**

     * 方法:成长

     * @param   sun太阳

     * @return 成长信息

     * @throws  Exception

     */

    @Override

    publicString grow(String sun)throwsException {

       System.out.println(sun +"正在朝阳这苹果树呢!");

       return"";

    }

}

 

/**

 * 类:红苹果

 * 继承:苹果

 */

public classRedApple extendsAbstractApple {

 

    //名字

    privateStringname;

    //颜色

    publicStringcolor;

    //个数

    protected int size;

    //单个售价

    public static final int price= 10;

   

    /**

     * 无参构造函数

     * 一定声明为public类型,否则getConstructors无法得到

     */

    publicRedApple() {

       super();

       System.out.println("初始化[RedApple]无参构造函数");

       setName("红富士");

       color= "红色";

       size= 5;

    }

   

    /**

     * 有参构造函数

     * @param name 名字

     * @param color颜色

     * @param size 个数

     */

    publicRedApple(String name, String color,int size) {

       this.setName(name);

       this.name= name;

       this.color= color;

       this.size= size;

    }

    /**

     * 获得:红苹果名称

     */

    publicString getName() {

       returnname;

    }

    /**

     * 设置:红苹果名称

     */

    public void setName(String name) {

       this.name= name;

    }

 

    /**

     * 方法:成长

     * @param   sun太阳

     * @return 成长信息

     * @throws  Exception

     */

    @Override

    publicString grow(String sun)throwsException {

       return super.grow(sun);

    }

    /**

     * 构建消息

     * @param   sun太阳

     * @return 成长信息

     */

    privateString buildMessage(String sun){

       return"红富士苹果正在享受"+ sun +"呢!";

    }

}

 

二.   传统的 RTTI

 

传统的 RTTI严格的说,反射也是一种形式的 RTTI,不过,一般的文档资料中把 RTTI 和反射分开,因为一般的,大家认为 RTTI 指的是传统的 RTTI,通过继承和多态来实现,在运行时通过调用超类的方法来实现具体的功能(超类会自动实例化为子类,或使用 instance of)。

 

传统的 RTTI 有 3 种实现方式:

1.      向上转型或向下转型(upcasting and downcasting) ,在 java 中,向下转型(父类转成子类)需要强制类型转换

2.      Class 对象(用了 Class 对象,不代表就是反射,如果只是用 Class 对象 cast 成指定的类,那就还是传统的 RTTI)

3.      instanceof 或isInstance()

 

传统的 RTTI 与反射最主要的区别,在于 RTTI 在编译期需要.class 文件,而反射不需要。传统的 RTTI 使用转型或 Instance 形式实现,但都需要指定要转型的类型,比如:

public voidrtti(Object obj) throwsClassNotFoundException {

       //类型转换

       IFruitredApple = (RedApple)obj;

       //instanceof判断

       if (objinstanceof RedApple){}

}

注意其中的 obj 虽然是被转型了,但在编译期,就需要知道要转成的类型RedApple,也就是需要 RedApple的.class 文件。相对的,反射完全在运行时在通过 Class 类来确定类型,不需要提前加载RedApple的.class 文件

 

三.   反射

 

摘要

Reflection 是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。

一句简单的话:反射就是把Java类中的各种成分映射成相应的java类。

 

那到底什么是反射(Reflection)呢?反射有时候也被称为内省(Introspection),事实上,反射,就是一种内省的方式,Java 不允许在运行时改变程序结构或类型变量的结构,但它允许在运行时去探知、加载、调用在编译期完全未知的 class,可以在运行时加载该 class,生成实例对象(instance object),调用 method,或对 field 赋值。这种类似于“看透”了 class 的特性被称为反射(Reflection),我们可以将反射直接理解为:可以看到自己在水中的倒影,这种操作与直接操作源代码效果相同,但灵活性高得多。

关于 Java 的反射 API,没必要去记忆,可以在任何 JDK API 中查询即可:

Class 类:http://www.ostools.net/uploads/apidocs/jdk-zh/java/lang/Class.html

reflect 包:http://www.ostools.net/uploads/apidocs/jdk-zh/java/lang/reflect/package-summary.html

 

四.   反射的实现方式

 

package com.xmm.trri;

 

import java.lang.reflect.Array;

import java.lang.reflect.Constructor;

import java.lang.reflect.Field;

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

import java.lang.reflect.Modifier;

import java.util.Arrays;

 

public classTestApple {

 

    public static voidmain(String[] args) {

      

       try {

           //获得类对象

           Class clazz = Class.forName("com.xmm.trri.RedApple");

           printInfo("获得类对象",clazz);

           /*--获得类对象: class com.xmm.trri.RedApple--*/

          

           // 获得超类

           Class superClass =clazz.getSuperclass();

           printInfo("获得超类",superClass);

           /*--获得超类: classcom.xmm.trri.AbstractApple--*/

          

           // 获得所有父接口

           Class[] interfaces =clazz.getInterfaces();

           printInfo("获得所有父接口",Arrays.toString(interfaces));

           /*--获得所有父接口: []--???--*/

          

           // 实例化

           RedApple redApple = (RedApple)clazz.newInstance();

           printInfo("实例化",redApple);

           /*--实例化: com.xmm.trri.RedApple@961dff--*/

          

           // 获得访问属性为 public的构造方法

           Constructor[] constructors =clazz.getConstructors();

           printInfo("获得构造方法",Arrays.toString(constructors));

           /*--获得构造方法: [public com.xmm.trri.RedApple(),

            * public com.xmm.trri.RedApple(java.lang.String,java.lang.String,int)]--*/

          

           // 获得指定参数的构造方法

           Constructor constructor =clazz.getDeclaredConstructor(

                                   String.class, String.class,int.class);

           printInfo("获得指定构造方法",constructor);

           /*--获得指定构造方法: publiccom.xmm.trri.RedApple(java.lang.String,java.lang.String,int)--*/

          

           // 获得方法,getMethod只能获得 public 方法,包括父类和接口继承的方法

           Method method = clazz.getMethod("grow", String.class);

           printInfo("获得公有方法",method);

           /*--获得公有方法: public java.lang.Stringcom.xmm.trri.RedApple.grow(java.lang.String)

            *            throws java.lang.Exception--*/

          

           // 调用方法

           method.invoke(redApple, "中午太阳");

           /*--中午太阳正在照耀这苹果树呢!--*/

          

          

           // 获得修饰符,包括 private/public/protect,static

           String modifier = Modifier.toString(method.getModifiers());

           printInfo("获得方法修饰符",modifier);

           /*--获得方法修饰符: public--*/

          

           // 获得参数类型

           Class[] paramTypes =method.getParameterTypes();

           printInfo("获得方法参数类型",Arrays.toString(paramTypes));

           /*--获得方法参数类型: [class java.lang.String]--*/

          

           // 获得返回值类型

           Class returnType =method.getReturnType();

           printInfo("获得方法返回值类型",returnType);

           /*--获得方法返回值类型: class java.lang.String--*/

          

           // 获得异常类型

           Class[] excepTypes =method.getExceptionTypes();

           printInfo("获得方法异常类型",Arrays.toString(excepTypes));

           /*--获得方法异常类型: [classjava.lang.Exception]--*/

          

           // 调用私有方法,getDeclaredMethod获得类自身的方法,

           // 包括 public,protect,private方法

           Method method2 =clazz.getDeclaredMethod("buildMessage",String.class);

           // 暴力反射

           method2.setAccessible(true);

           String result =(String)method2.invoke(redApple, "中午太阳");

           printInfo("获得私有方法",result);

           /*--获得私有方法:红富士苹果正在享受中午太阳呢!--*/

          

           // 获得全部字段

           Field[] fields = clazz.getFields();

           printInfo("获得类全部字段",Arrays.toString(fields));

           /*--

            * 获得类全部字段:[public java.lang.String com.xmm.trri.RedApple.color,

               [public static final intcom.xmm.trri.RedApple.price]

            * --*/

          

           // 获得类自身定义的指定字段

           Field field = clazz.getDeclaredField("name");

           printInfo("获得自身指定的名称字段",field);

           /*--获得自身指定的名称字段: private java.lang.String com.xmm.trri.RedApple.name--*/

          

           // 获得类及其父类,父接口定义的 public字段

           Field field2 = clazz.getField("color");

           printInfo("获得公有的指定名称字段",field2);

           /*--获得公有的指定名称字段: public java.lang.Stringcom.xmm.trri.RedApple.color--*/

          

           // 获得字段权限修饰符,包括private/public/protect,static,final

           String fieldModifier = Modifier.toString(field2.getModifiers());

           printInfo("获得字段权限修饰符",fieldModifier);

           /*--获得字段权限修饰符: public--*/

          

           // 操作数组

           int[] exampleArray = { 1, 2, 3, 4, 5 };

          

           // 获得数组类型

           Class componentType =exampleArray.getClass().getComponentType();

           printInfo("数组类型",componentType.getName());

           /*--数组类型:int--*/

          

           // 获得长度

           printInfo("数组长度",

           Array.getLength(exampleArray));

           /*--数组长度: 5--*/

          

           // 获得指定元素

           printInfo("获得数组元素",

           Array.get(exampleArray, 2));

           /*--获得数组元素: 3--*/

          

           // 修改指定元素

           Array.set(exampleArray, 2, 6);

           printInfo("修改数组元素",Arrays.toString(exampleArray));

           /*--修改数组元素: [1, 2, 6, 4, 5]--*/

          

           // 获得当前的类加载器

           printInfo("获得当前类加载器",

           redApple.getClass().getClassLoader().getClass().getName());

           /*--获得当前类加载器:sun.misc.Launcher$AppClassLoader--*/

          

          

       } catch (ClassNotFoundException e) {

           e.printStackTrace();

       }catch (IllegalAccessException e) {

           e.printStackTrace();

       } catch (IllegalArgumentException e) {

           e.printStackTrace();

       } catch (InvocationTargetException e) {

           e.printStackTrace();

       }catch (NoSuchFieldException e) {

           e.printStackTrace();

       } catch (SecurityException e) {

           e.printStackTrace();

        }catch (NoSuchMethodException e) {

           e.printStackTrace();

       } catch (InstantiationException e) {

           e.printStackTrace();

       }

      

    }

   

    public static voidprintInfo(String info, Object obj) {

       if (obj.getClass().isArray()) {

           System.out.println(info +": ");

           int length = Array.getLength(obj);

           System.out.println("Array Size: " + length);

           for (int i = 0; i < length; i++) {

              System.out.print("Array[" + i +"]: " + Array.get(obj, i)

                     + ", ");

           }

           if (length != 0)

              System.out.println();

       }

       System.out.println(info +": " + obj.toString());

    }

}

// Modifier 类提供了static方法和常量,对类和成员访问修饰符进行解码

// Array类提供了动态创建和访问 Java数组的方法

 

通过上面的代码,可以清晰的理解如何“在水中看到自己”,不过需要注意的有几点:

1.      在 java 的反射机制中,getDeclaredMethod 得到的是全部方法,getMethod 得到的是公有方法;

2.      反射机制的 setAccessible 可能会破坏封装性,可以任意访问私有方法和私有变量;

3.      setAccessible 并不是将 private 改为 public,事实上,public 方法的accessible属性也是 false 的,setAccessible 只是取消了安全访问控制检查,所以通过设置 setAccessible,可以跳过访问控制检查,执行的效率也比较高。参考:

http://blog.csdn.net/devilkin64/article/details/7766792

 

五.   反射的性能

反射机制给予 Java 开发很大的灵活性,但反射机制本身也有缺点,代表性的缺陷就是反射的性能,一般来说,通过反射调用方法的效率比直接调用的效率要至少慢一倍以上。关于性能的问题,可以参考这篇博客

http://blog.csdn.net/l_serein/article/details/6219897

 

六.   反射与设计模式

反射的一个很重要的作用,就是在设计模式中的应用,包括在工厂模式和代理模式中的应用。关于这一方面,我会在后续的文章中介绍,有兴趣的朋友也先可以参考这篇文章

http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html中关于动态代理模式实现方法的文章。

 

七.   其他

1.       将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"。

 

privateStringa = "a-b";

   

    protectedStringb = "b-b";

   

    publicStringc = "c-b";

   

    publicString toString(){

       return"a:" + a + "--b:" + b + "--c:" + c;

    }

   

    public static voidmain(String[] args)throwsInstantiationException, IllegalAccessException, ClassNotFoundException,IllegalArgumentException, SecurityException, InvocationTargetException,NoSuchMethodException {

       //1. 获得Class对象

       Class clazz = Class.forName("com.ldcms.hotel.action.Test");

       //2. new一个对象

       Test test = (Test) clazz.newInstance();

       //3. 获得对象的所有字段

       Field[] fields =clazz.getDeclaredFields();

       //4. 遍历字段集

       for(Field field : fields) {

           //5.去除安全安全检查

           field.setAccessible(true);

           //6.获得字段的值

           String value = (String)field.get(test);

           //7.替换值

           if(value.contains("b")){

              value.replace("b","a");

              //8.重新设置值

              field.set(test, value.replace("b","a"));

           }

       }

       //9. 打印输出

       System.out.println(clazz.getDeclaredMethod("toString",null).invoke(test,null));

       //结果:a: a-a--b: a-a--c:c-a

}

 

2.       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 Objectinvoke(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})形式。

3.       用反射方法执行某个类中的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方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。

解决办法:

mainMethod.invoke(null,newObject[]{new String[]{"xxx"}});

mainMethod.invoke(null,(Object)newString[]{"xxx"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了

 

你可能感兴趣的:(笔记)