java反射机制总结

经过多方面的资料搜集整理,写下了这篇文章,本文主要讲解java的反射和内省机制,希望对大家有点帮助,也希望大家提出不同的看法!

1).反射是java的基础技术,在我们用到的框架如:struts,spring,hibernate,junit等都用到了反射技术,所以了解反射对我们来说也很重要。要了解反射技术,必须要了解一个类Class类。

2).平时我们定义class的时候都是小写,这里讲的Class是大写,此时它代表一类事物,java类用于描述一类事物的共性,如该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这个类的实例对象来确定,不同的事物对象有不同的属性值。java程序中的java类,属于同一类事物,描述这类事物的Java类名就是Class.,就比如众多的人可以用一个Person类表示一样,众多的java类用Class来表示。

3).Class类代表Java类,它(Class类)的各个实例对象分别对应各个类在内存中的字节码,那么什么叫字节码呢?java源程序被编译成一堆二进制的class代码,当我们需要用到某个类时,首先把类的字节码加载到内存中来,然后再用该字节码去复制一个个的实例对象来,每份字节码都是Class类的实例对象,即Class类的实例对象就代表内存中字节码。
如:
public static void main(String[] args) throws Exception {
    Class cls1 = Date.class;//cls1代表Date类在内存中的字节码
    Class cls2 = String.class;//cls2代表String类在内存中的字节码
    Class cls3 = String.class;//cls3代表String类在内存中的字节码
    System.out.println(cls2 == cls3);//true:同一份字节码在内存中的地址相同
}

4).一个类被类加载器加载到内存中,占用一片存贮空间,这个空间里的内容就是这个类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型就是Class类型.

5).如何得到各个字节码对应的实例对象?(Class类型):即得到字节码的三种方式:
.)类名.class 例如:System.class//写程序的时候就知道类的名称
.)对象.getClass() 例如:new Date().getClass()
.)Class.forName() 例如:Class.forName("java.util.Date");//类的字符串的完整名称
做反射的时候常用第三种,因为写源程序的时候不知道类的名称,在运行的时候根据传递的字符串来创建.
例如:
public static void main(String[] args) throws Exception {
    Date nowDate = new Date();
    Class cls1 = nowDate.getClass();//getClass()方法返回创建该对象的字节码
    Class cls2 = Date.class;
    Class cls3 = Class.forName("java.util.Date");
    System.out.println(cls1);// class java.util.Date
    System.out.println(cls1 == cls2);// true
    System.out.println(cls1 == cls3);// true
    //这说明三个应用变量引用的是同一份字节码
}

例如:
public static void main(String[] args) throws Exception {
    Date nowDate = new Date();
    Date tomDate = new Date();

    System.out.println(nowDate == tomDate);// false
    System.out.println(nowDate.getClass() == tomDate.getClass());// true
    //同一个类的字节码是相同的(即使是不同的实例对象.getClass()依然是相等的).
}

6)Class.forName()的作用:得到这个类的字节码,包含下面两种情况
.这个类的字节码已经加载到Java虚拟机中,只需要找到返回.
.这个类的字节码在Java虚拟机没找到,于是用类加载器加载,然后把类的字节码缓存起来再返回.

.7)8个基本类型和一个void共9个预定义的Class实例对象
例如:
public static void main(String[] args) throws Exception {
    System.out.println(int.class.isPrimitive());//true
    System.out.println(int.class == Integer.class);//false
    System.out.println(int.class == Integer.TYPE);//true,TYPE表示基本类型 Class实例。
    System.out.println(void.class.isPrimitive());//true
    System.out.println(int[].class.isArray());//true
}
   
.8)总之,在源程序中出现的任何类型都有各自对应得Class对象实例,例如:int,int[],void等

 

1).通俗的讲,反射就是把java类中的各种成分映射成相应的java类.如一个java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等信息也用一个个的java类表示。那么java类的Class类显然要提供一系列的方法来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应的类的实例对象来表示,它们是Field,Method,Construtor,Package等。

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


2).一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示。

3).Constructor类:Constructor类代表某个类中的一个构造方法.

.1)得到某个类所有的构造方法:例如: Constructor[] cons = String.class.getConstructors();
.2)得到某一个构造方法:例如: Constructor cons = String.class.getConstructor(StringBuffer.class);//获得构造方法时要调用的参数类型,因为要识别构造方法,必须靠参数类型。
.3)创建实例对象:
通常情况下:String str = new String(new StringBuffer("abc"));
反射方式: String str = (String) cons.newInstance(new StringBuffer("abc"));//调用获得方法时要用到上面相同类型的实例对象
.4)Class.newInstance()方法,它是把class - constructor - new obj 简化为 class - newInstance
原理:该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象,该方法内部用到了缓存机制来保持默认构造方法的实例对象,相当于调用 无参的构造方法,反射会导致系统性能下降.所以使用反射要慎重。
例如: String sr = String.class.newInstance();

相关代码:
public static void main(String[] args) throws Exception {
    Constructor constructor = String.class.getConstructor(StringBuffer.class);
    String str2 =(String)constructor.newInstance(new StringBuffer("abc"));
    //newInstance()返回的是一个Object,所以要用类型转换
    System.out.println(str2);
   
    String str3 = (String)constructor.newInstance("abc");
    System.out.println(str3);//有异常:argument type mismatch
    //获得构造方法时要调用的参数类型和调用获得方法时要有相同的参数类型
}

4).Field类:Field类代表类中的一个成员变量
相关代码一:
public class Point {
public Integer x;
private Integer y;

public Point(Integer x, Integer y) {
    super();
    this.x = x;
    this.y = y;
}
}

public static void main(String[] args) throws Exception {
    Point reflect = new Point(2, 5);
    Field fieldX = Point.class.getField("x");
    // 此时fieldX的值不是5,fieldX不是对象身上的变量,而是类上,要用它去取对象身上的值,必须和实例对象结合使用。

    Integer valueX = (Integer) fieldX.get(reflect);
    System.out.println(valueX);// 2

//    Field fieldY = Point.class.getField("y");
//    // 由于属性y的修饰符是private,所以程序运行到此处就有异常,说该属性不存在:java.lang.NoSuchFieldException: y
//    Integer valueY = (Integer) fieldY.get(reflect);

//    // 下面的方法可以解决无法查看私有属性的方法
//     Field fieldY2 = Point.class.getDeclaredField("y");
//    //getDeclaredField():只要声明的变量都可以查看,运行到此处无异常
//     Integer valueY2 = (Integer) fieldY2.get(reflect);
//    // 在这里会有异常,属性虽然存在,但无法访问:Class com.sun.Reflect can not access a member of class com.sun.Point with modifiers "private"


    // 暴力反射,即使设为private的属性变量依然可以访问,
    Field fieldY3 = Point.class.getDeclaredField("y");
    fieldY3.setAccessible(true);
    Integer valueY3 = (Integer) fieldY3.get(reflect);
    System.out.println(valueY3);// 5
    //一个代表看不见,一个代表看见了但取出到值,就类如:一我看不到别人的钱,而是我看到了,但是用不到

相关代码二:
public class Point {
public String str1 = "who";
public String str2 = "when";
public String str3 = "where";
public Integer num = 5;
public boolean istrue = true;

public String toString() {
    return str1 + "-" + str2 + "-" + str3 + "-" + num + "-" + istrue;
}
}

public class Reflect {
public static void main(String[] args) throws Exception {
    Point point = new Point();
    System.out.println(point);// who-when-where-5-true
    changeFieldValues(point);
    System.out.println(point);// Who-When-Where-5-true:把"w"改成了大写的"W"
    //我们看到了反射可以任意改变属性的值,这种应用在很多框架中都有使用!
}

private static void changeFieldValues(Point point) throws IllegalAccessException {
    Field[] fields = Point.class.getFields();
    for (Field field : fields) {
      if (field.getType() == String.class) {
        String oldVal = (String) field.get(point);
        String newVal = oldVal.replace("w", "W");
        field.set(point, newVal);
      }
    }
}
}

5).Method:Method类代表某个类中的一个构造方法;
.1)得到某个类中某一个方法:例如:Method stringCharAt = String.class.getMethod("charAt", int.class);
.2)创建实例对象:
通常方式: System.out.println("abcd".charAt(1));
反射方式: System.out.println(stringCharAt.invoke("abcd", 1));//b,如果invoke得第一个参数为空,那么该method对应的是一个静态方法
.3)invoke()在JDK1.4,和1.5中的参数形式不同。
相关代码:
public static void main(String[] args) throws Exception {
    String str = "welcome!";
    Method charAt = String.class.getMethod("charAt", int.class);
    System.out.println(charAt.invoke(str, 0));// w
//    System.out.println(myMethod.invoke(null, 0));// myMethod为静态方法,第一个参数不用指定对象
}

6) 数组中的反射
使用 java.lang.reflect.Array实现。
Object obj = null;
printObject(obj);

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);
    }
}

.1) 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
.2) 代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
.3) 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当作Object类型使用,又可以当作Object[]类型使用。
.4) Array.asList()方法处理int[]和String[]时的差异,见下面的示例:
   int iArr[] = new int[]{3,4,5,6,7};
   System.out.println(Arrays.asList(iArr));       // 输出:[[I@c17164],而不是:3,4,5,6
   System.out.println(Arrays.asList(new String[]{"abc","def"})); // 输出:[abc, def]
原因为int[]被当作了一个Object,而不是Object[]。

1).内省(Introspector)是 Java 语言对 Bean 类属性、事件的一种缺省处理方法。例如类 A 中有属性 name, 那我们可以通过 getName,setName 来得到其值或者设置新的值。通过 getName/setName 来访问 name 属性,这就是默认的规则。 Java 中提供了一套 API 用来访问某个属性的 getter/setter 方法,通过这些 API 可以使你不需要了解这个规则(但你最好还是要搞清楚),这些 API 存放于包 java.beans 中。

2).直接通过属性的描述器java.beans.PropertyDescriptor类,来访问属性的getter/setter 方法;

相关代码:
public class Point {
private Integer x;
private Integer y;

public Point(Integer x, Integer y) {
    super();
    this.x = x;
    this.y = y;
}

public Integer getX() {
    return x;
}

public void setX(Integer x) {
    this.x = x;
}

public Integer getY() {
    return y;
}

public void setY(Integer y) {
    this.y = y;
}
}

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

public class Reflect {

public static void main(String[] args) throws Exception {
    Point point = new Point(2, 5);
    String proName = "x";

    getProperty(point, proName);
    setProperty(point, proName);

}

private static void setProperty(Point point, String proName) throws Exception {
    PropertyDescriptor proDescriptor = new PropertyDescriptor(proName, Point.class);
    Method methodSetX = proDescriptor.getWriteMethod();
    methodSetX.invoke(point, 8);
    System.out.println(point.getX());// 8
}

private static void getProperty(Point point, String proName) throws Exception {
    PropertyDescriptor proDescriptor = new PropertyDescriptor(proName, Point.class);
    Method methodGetX = proDescriptor.getReadMethod();
    Object objx = methodGetX.invoke(point);
    System.out.println(objx);// 2
}
}

3).通过类 Introspector 来获取某个对象的 BeanInfo 信息,然后通过 BeanInfo 来获取属性的描述器( PropertyDescriptor ),通过这个属性描述器就可以获取某个属性对应的 getter/setter 方法,然后我们就可以通过反射机制来调用这些方法。

相关代码:
把2中的getProperty()修改成如下形式:
private static void getProperty(Point point, String proName) throws Exception {
    BeanInfo beanInfo = Introspector.getBeanInfo(point.getClass());
    PropertyDescriptor[] proDescriptors = beanInfo.getPropertyDescriptors();
    for(PropertyDescriptor prop: proDescriptors){
      if(prop.getName().equals(proName)){
        Method methodGetx = prop.getReadMethod();
        System.out.println(methodGetx.invoke(point));//8
        break;
      }
    }
}

4).我们又通常把javabean的实例对象称之为值对象(Value Object),因为这些bean中通常只有一些信息字段和存储方法,没有功能性方法。一个JavaBean类可以不当JavaBean用,而当成普通类用。JavaBean实际就是一种规范,当一个类满足这个规范,这个类就能被其它特定的类调用。一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量(javabean的成员变量通常都是私有private的)。

5).除了反射用到的类需要引入外,内省需要引入的类如下所示,它们都属于java.beans包中的类,自己写程序的时候也不能忘了引入相应的包或者类。
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;

6).下面讲解一些开源的工具类Beanutils,需要额外下载的,commons-beanutils.jar,要使用它还必须导入commons-logging.jar包,不然会出异常;
相关代码一:
public static void main(String[] args) throws Exception {
    Point point = new Point(2, 5);
    String proName = "x";
    BeanUtils.setProperty(point, proName, "8");
    System.out.println(point.getX());// 8
    System.out.println(BeanUtils.getProperty(point, proName));// 8
    System.out.println(BeanUtils.getProperty(point, proName).getClass().getName());// java.lang.String

    BeanUtils.setProperty(point, proName, 8);
    System.out.println(BeanUtils.getProperty(point, proName).getClass().getName());// java.lang.String
}
//我们看到虽然属性x的类型是Integer,但是我们设置的时候无论是Integer还是String,BeanUtils的内部都是当成String来处理的。

相关代码二:
BeanUtils支持javabean属性的级联操作;
public static void main(String[] args) throws Exception {
    Point point = new Point(2, 5);//在point中加一个属性 private Date birth = new Date();并产生setter/getter方法
    String proName = "birth";
    Date date= new Date();
    date.setTime(10000);
    BeanUtils.setProperty(point, proName, date);
    System.out.println(BeanUtils.getProperty(point, proName));
   
    BeanUtils.setProperty(point, "birth.time", 10000);
    System.out.println(BeanUtils.getProperty(point, "birth.time"));//10000
}
//之所以可以 BeanUtils.setProperty(point, "birth.time", 10000);这样写,那是因为Date类中有getTime()和setTime()方法,即Date类中相当于有time这个属性。

相关代码三:
BeanUtils和PropertyUtils对比:
public static void main(String[] args) throws Exception {
    Point point = new Point(2, 5);
    String proName = "x";
    BeanUtils.setProperty(point, proName, "8");
    System.out.println(BeanUtils.getProperty(point, proName));//8
    System.out.println(BeanUtils.getProperty(point, proName).getClass().getName());//java.lang.String
   
// PropertyUtils.setProperty(point, proName, "8");//exception:argument type mismatch
    PropertyUtils.setProperty(point, proName, 8);
    System.out.println(PropertyUtils.getProperty(point, proName));//8
    System.out.println(PropertyUtils.getProperty(point, proName).getClass().getName());//java.lang.Integer
}
//BeanUtils它以字符串的形式对javabean进行转换,而PropertyUtils是以原本的类型对javabean进行操作。如果类型不对,就会有argument type mismatch异常。

6).理解了相应的原理,那些现成的工具用起来就会更舒服,如Beanutils与PropertyUtils工具。这两个工具设置属性的时候一个主要区别是PropertyUtils.getPropety方法获得的属性值的类型为该属性本来的类型,而BeanUtils.getProperty则是将该属性的值转换成字符串后才返回。

总结

Web 开发框架 Struts 中的 FormBean 就是通过内省机制来将表单中的数据映射到类的属性上,因此要求 FormBean 的每个属性要有 getter/setter 方法。但也并不总是这样,什么意思呢?就是说对一个 Bean 类来讲,我可以没有属性,但是只要有 getter/setter 方法中的其中一个,那么 Java 的内省机制就会认为存在一个属性,比如类中有方法 setMobile ,那么就认为存在一个 mobile 的属性。

将 Java 的反射以及内省应用到程序设计中去可以大大的提供程序的智能化和可扩展性。有很多项目都是采取这两种技术来实现其核心功能,例如我们前面提到的 Struts ,还有用于处理 XML 文件的 Digester 项目,其实应该说几乎所有的项目都或多或少的采用这两种技术。在实际应用过程中二者要相互结合方能发挥真正的智能化以及高度可扩展性。

 

 

你可能感兴趣的:(java,bean,框架,struts,mobile)