一、概述
反射是JDK1.2
以来就有的特性。学习反射,首先要了解Class
这个类,
1.Class类
Java中的类描述一类事物的共性,该类事物有什么属性,没有什么属性。但是这个类不管这些属性的具体值,类只负责描述这些属性。属性的具体值,是由这个类的具体实例对象来确定的。这些类,也可以理解为Java中的同一类事物,而用来描述这些类的类,就是Class类
。每一个Java类都会有类名,包名,成员变量,方法,父类等等,这些属性,都可以在Class类
中获取。如何得到Class类
的对象?首先,Class
对象对应的是内存中的字节码文件。字节码文件是源代码编译好的class文件
,当要使用某个类的对象时,要将class文件加载到内存,用这个字节码复制出一个一个对象。Java程序中用了几个类,就有几份字节码;这些字节码就是Class类
的对象。
2.获取Class类的对象
获取字节码对应的实例对象有三种方法:
1)类名.calss;如:System.class;
2)对象.getClass();如:new Date().getClass();
3)Class.forName("类名");如:Class.forName("java.util.Date");
扩展:
Class.forName("java.lang.String");
forName()方法可以返回指定类名的字节码。得到一个类的字节码有两种情况:
1)这个类的字节码已经加载到内存,那么直接找到这个字节码,返回;
2)这个类的字节码还没有在虚拟机里,那么用类加载器加载,然后缓冲起来,forName()方法返回这个字节码
3.九个预定于Class对象
八个基本类型对应的Class对象
,void
也有对应的Class对象
,即void.class
。下面用一个示例来演示Class类
。
示例代码:
package com.heisejiuhuche.reflection;
public class RelectionDemo {
public static void main(String[] args) throws Exception {
String str = "abc";
/* 通过三种方式获取String类的字节码 */
Class cls1 = str.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");
/* 三种方式获取的字节码文件都是同一份 */
System.out.println(cls1 == cls2);
System.out.println(cls1 == cls3);
/* cls1字节码对应的String类不是基本数据类型 */
System.out.println(cls1.isPrimitive());
/* int类型的Class对象是基本数据类型 */
System.out.println(int.class.isPrimitive());
/* int类型和Integer类型对应不同的类,也有不同的字节码 */
System.out.println(int.class == Integer.class);
/* int类型和Integer.TYPE都表示int类型的基本数据类型 */
System.out.println(int.class == Integer.TYPE);
/* 数组不是基本数据类型 */
System.out.println(int[].class.isPrimitive());
/* Class对象可以通过isArray()方法判断其是否为数组 */
System.out.println(int[].class.isArray());
}
}
注意:
只要是在源程序中出现的类型,都有各自的Class实例对象,例如:int[],void…
二、反射
反射就是把Java类
中的各种成分映射成相应的Java类
。例如,一个Java类
用一个Class类
的对象来表示;类中的组成部分,成员变量,方法,构造方法,包等等信息也用一个个的Java类
来表示。Field
、Method
、Constructor
、Package
等类,代表了一个Java类
中的各个成分。如,Method
代表了一个类中的所有方法;具体的方法1
,对应的是Method
这个类的实例对象methodObj1
。
三、反射中的类
1.Constructor类
代表一个类中的构造方法。
1)得到某个类所有的构造方法
Constructor[] constructor = Class.forName("java.lang.String").getConstructors();
2)得到某个单独的构造方法
Constructor constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
3)创建实例对象
String string = (String)constructor.newInstance(new StringBuffer("abc"));
注意:
-String类有很多的构造方法,那么单独获取一个构造方法,靠的就是指定这个构造方法中的参数;上面的例子,就是用了String类接收StringBuffer作为参数的构造方法;-JDK1.5之后出现了可变参数,getConstructor()方法接收的是可变参数列表,参数可以是一个,可以是多个;这些参数,都代表这String类当中的一个构造方法;
-使用String的构造方法创建String对象的时候,调用constructor对象的newInstance()方法,传入一个StringBuffer作为参数,来构造一个string对象;根据上一条所说,编译时期不知道这个constructor对象代表的是哪个类的构造方法,只有到现在这个运行时才能明确;同时,newInstance方法返回值是Object类型,所以要加上一个String的类型转换
-得到构造方法的时候需要类型,调用这个构造方法的时候同样需要传递同样类型的对象
4)Class.newInstance()方法
String obj = (String)Class.forName("java.lang.String").newInstance();
注意:
Class类中也有一个newInstance()方法;这个方法用于获取某个类的无参构造方法;这是为了提供简便;用反射机制创建一个类的对象要先获取这个类的Class对象;然后通过Class对象获取constructor对象;最后由constructor创建Object;如果可以省去中间步骤,直接由Class对象获取构造方法,可以提高一些效率;Class类的newInstance()方法中运用了缓存机制,将第一次获取的空参数构造方法缓存起来;如果下次还需要调用,就不需要获取,直接从缓存中调用,提高运行速度;也从侧面说明反射比较消耗性能示例代码:
package com.heisejiuhuche.reflec;
import java.lang.reflect.Constructor;
public class ReflectNewInstanceDemo {
public static void main(String[] args) throws Exception {
/* 获取String类的Class对象cls1 */
Class cls1 = String.class;
/* 获取String类的接收StringBuffer的构造方法 */
Constructor constructor = cls1.getConstructor(StringBuffer.class);
/* 调用newInstance方法创建String对象str,因为返回值为Object
* 所以要做类型转换
*/
String str = (String)constructor.newInstance(new StringBuffer("abc"));
System.out.println(str);
}
}
程序运行结果:abc
2.Field类
Field类代表某个类中的一个成员变量
使用Field类获取类中的成员:
package com.heisejiuhuche.reflec;
import java.lang.reflect.Field;
public class ReflectFieldDemo {
public static void main(String[] args) throws Exception {
/* 创建Point对象 */
Point pt1 = new Point(3, 5);
/* 获取Point类的成员变量y */
Field fieldY = pt1.getClass().getField("y");
/* fieldY不是Point对象上的变量,是Point类上的变量
* 它没有值;要获取它的值,只能指定去获取fieldY在
* 某个具体对象上的值;下面获取的是其在pt1上的值
*/
System.out.println(fieldY.get(pt1));
/* 由于x是私有成员变量,只能通过getDeclaredField方法获取 */
Field fieldX = pt1.getClass().getDeclaredField("x");
/* 由于x的私有属性,必须进行暴力反射,通过setAccessible方法让其可以被访问 */
fieldX.setAccessible(true);
/* 获取x的值 */
System.out.println(fieldX.get(pt1));
}
}
/* 声明一个Point类,该类有一个私有成员变量x,一个共有成员变量y
* 同时拥有一个两个参数的构造方法
*/
class Point {
private int x;
public int y;
public Point(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
程序运行结果:
5
3
3.练习
将任意一个对象中的所有String
类型的成员变量所对应的字符串内容中的"b"
改成"a"
示例代码:
package com.heisejiuhuche.reflec;
import java.lang.reflect.Field;
/**
* 修改Point1类中所有String类成员变量 将字符串中的‘a’改成‘b’
*
* @author jeremy
*
*/
public class ReflectChangeValueTest {
public static void main(String[] args) throws Exception {
/* 创建Point1对象 */
Point pt1 = new Point(3, 5);
changeValue(pt1);
System.out.println(pt1);
}
private static void changeValue(Object obj) throws Exception {
/* 获取pt1对象的所有成员变量 */
Field[] fileds = obj.getClass().getFields();
/* 遍历成员,如果有类型是String的,就获取其值进行更改 */
for(Field field : fileds) {
/* 如果field的类型和String类型是同一字节码,说明是String类型的成员 */
if(field.getType() == String.class) {
/* 替换字符串 */
String oldValue = (String)field.get(obj);
String newValue = oldValue.replace('b', 'a');
/* 将类中的成员的值改成新的值 */
field.set(obj, newValue);
}
}
}
}
class Point {
private int x;
public int y;
public String str1 = "ball";
public String str2 = "basketball";
public String str3 = "itheima";
public Point(int x, int y) {
super();
this.x = x;
this.y = y;
}
/* 复写toString方法 */
@Override
public String toString() {
return str1 + ":" + str2 + ":" + str3;
}
}
程序运行结果:aall:aasketaall:itheima
4.Method类Method
类代表某个类中的一个成员方法
1)得到类中的某一个方法
Method method = Class.forName("java.lang.String").getMethod("CharAt", int.class);
2)调用方法
System.out.println(method.invoke(str, 1));
示例代码:
package com.heisejiuhuche.reflec;
import java.lang.reflect.Method;
public class ReflectMethodDemo {
public static void main(String[] args) throws Exception {
String str = "abcde";
/* 得到String类的Method对象,获取String类的接收int类型参数的indexOf方法 */
Method method = Class.forName("java.lang.String").getMethod("indexOf", int.class);
/* 调用Method对象method的invoke方法去调用indexOf方法 */
System.out.println(method.invoke(str, 'c'));
}
}
程序运行结果:2
注意:
-如果invoke()方法的第一个参数为null,说名这个method对象对应的是一个静态方法,因为不需要对象进行调用-JDK1.5的invoke()方法可以接收可变参数,在JDK1.4版本,invoke()方法的第二个参数只能接收一个Object类型的数组;不过JDK是向下兼容的,在JDK1.5以后使用数组参数也可以
5.用反射执行某个类中的main方法
用反射的方式调用某个类的main方法,可以不用事先直到那个类的名称。这个类的名称,可以通过参数的方式传递给程序,然后在程序中,用反射的方法调用那个类的main方法。而传统方法调用一个类的main方法,必须事先知道那个类的名称,然后用静态调用的方式调用其main方法。
示例代码:
package com.heisejiuhuche.reflec;
import java.lang.reflect.Method;
public class ReflectMainMethodDemo {
public static void main(String[] args) throws Exception {
/* 运行之前不直到这个类的名称,将把这个类当作参数传递给这个程序
* 那么参数列表的第一个元素就是类名
*/
String className = args[0];
/* 获取传进来的这个类的字节码文件,并获取其main方法 */
Method methodMain = Class.forName(className).getMethod("main", String[].class);
/* 调用该类的main方法
* 在数组参数前加上(Object)是因为JDK1.5为了兼容低版本JDK,会将传递进main方法的
* String数组拆包,那么传递进去的就不是一个参数,而是三个,会出现参数个数异常
* 所以加上强制转换动作,为了告诉编译器,这里传递的是一个Object对象(因为数组也是
* Object)不需要进行拆包了,就当成一个参数,那么就不会出现异常
*/
methodMain.invoke(null, (Object)new String[] {"111", "222", "333"});
}
}
class Test {
public static void main(String[] args) {
for (String arg : args) {
System.out.println(arg);
}
}
}
程序运行结果:
111
222
333
6.数组的反射
每一个具有相同元素类型和维度的数组都属于同一个反射出来的Class
。
示例代码:
package com.heisejiuhuche.reflec;
import java.util.Arrays;
public class ReflectArrayDemo {
public static void main(String[] args) throws Exception {
int[] a1 = new int[]{2, 3, 4};
int[] a2 = new int[4];
int[][] a3 = new int[3][4];
String[] a4 = new String[]{"a", "b", "c"};
/* 相同类型,相同维度 返回 true */
System.out.println(a1.getClass() == a2.getClass());
/* 相同类型,不同维度 返回 false */
// System.out.println(a1.getClass() == a3.getClass());
/* 不同类型,相同维度 返回false */
// System.out.println(a1.getClass() == a4.getClass());
/* 获取a1数组的名字 [I */
System.out.println(a1.getClass().getName());
/* 获取a1和a4父类的名字java.lang.Object */
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(a4.getClass().getSuperclass().getName());
Object obj1 = a1;
Object obj2 = a3;
Object obj3 = a4;
// Object[] obj4 = a1;//int基本数据类型不能被转换为Object类型
Object[] obj5 = a3;
Object[] obj6 = a4;
/* 将数组a1转换成List 打印 结果为[[I@659e0bfd] */
System.out.println(Arrays.asList(a1));
/* 将数组a4 转换成List 打印 结果为 [a, b, c] */
System.out.println(Arrays.asList(a4));
}
}
注意:
-基本数据类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本数据类型的一维数组,既可以当作Object类型使用,又可以当作Object[]类型使用;当一个基本数据类型的一维数组作为Object使用时,可以理解为所有的对象都是Object,基本数据类型的一维数组也不例外;但是当将一维数组转换为Object[]数组类型时,意味着一个数组里面装的是Object类型的对象;那么,String[]数组里面装的是String对象,可以是Object;int[][]二维数组里面装的是int[]对象,也可以是Object;但是int[]一维数组里面装的是int基本类型数据,不可以是Object;所以int[]不能被当成Object[]使用;-Arrays.asList()方法,在JDK1.5之前接收的是一个Object[]数组作为参数;那么根据上一条所说,当传入一个String[]数组的时候,满足String[]数组可以当成Object[]数组的条件,就将String[]数组中的值分别拿出来装进List,再打印出来;而将int[]基本数据类型的数组传入的时候,int[]数组不能被当成Object[]数组使用,那么就会回到JDK1.5版本使用更高级的处理方式,将int[]数组当成是一个整体元素,不会将里面的元素再取出,即List里面装了一个元素,就是这个int[]类型的数组,所以无法打印出数组内的元素
用反射获取数组中的元素
数组有length
属性,有索引下标,可以设置数组的值等,现在通过数组的反射来操作数组
示例代码:
package com.heisejiuhuche.reflec;
import java.lang.reflect.Array;
public class ReflectArrayDemo2 {
public static void main(String[] args) {
int[] a1 = new int[]{2, 3, 4};
int[] a2 = new int[4];
int[][] a3 = new int[3][4];
String[] a4 = new String[]{"a", "b", "c"};
printObject(a4);
printObject("xyz");
}
/* 创建打印任何对象的方法 */
private static void printObject(Object obj) {
/* 得到obj对象的字节码 */
Class cls = obj.getClass();
/* 判断Class对象是否是数组的字节码对象 */
if(cls.isArray()) {
/* 如果是,使用反射得到数组的长度并打印 */
int len = Array.getLength(obj);
for(int i = 0; i < len; i++) {
System.out.println(Array.get(obj, i));
}
} else {
/* 如果不是,直接打印这个obj */
System.out.println(obj);
}
}
}
程序运行结果:
a
b
c
xyz
四、ArrayList-HashSet的比较及HashCode分析
1.概述ArrayList
是一种有顺序的集合,相当于一个数组。加入对象的时候,首先找到第一个空位,将对象的引用存储进来;第二个对象,就按顺序依次往后添加;当添加到相同对象的时候,就将相同的引用存储进来。ArrayList
是按照先后顺序依次添加对象。
HashSet
在存储的时候,首先会看集合中是否有相同的元素,如果有,就不会添加;如果要添加重复对象,一定要先删除原先的,再添加新的。
2.hashCode()方法的作用
如果要查找一个集合中是否包含某个对象,通常的做法是从第一个元素开始,逐一取出每个元素和目标元素进行比较,如果有相同的,就返回真,如果没有则返回假。那么,当一个集合中有很多很多元素的时候,譬如一万个;现在假设这个集合中没有包含目标元素,如果要进行查找,还是要从第一个元素开始,取出比较一万次,没有找到匹配,返回假。这样的效率特别低。hashCode()
方法可以避免这样的事情发生,提高程序效率。hashCode
称为哈希值,是由哈希算法生成的一个值。哈希算法将集合分成若干个存储的区域,每个对象都可以计算出一个哈希值,将这个哈希值进行分组,每组分别对应某个存储区域,根据一个对象的哈希值就可以确定该对象存储在哪个区域。如图:
HashSet
采用哈希算法存取对象。它内部通过对某个数字n取余的方式来对哈希值进行分组和划分对象的存储区域。每个对象都有继承自Object类
的hashCode()
方法。当要从HashSet
集合中查找某个对象时,Java先调用该对象的hashCode()
方法获取其哈希值,然后根据哈希值找到相应的存储区域,最后取出该区域中的每个元素与该对象进行equals()
方法的比较。这样就无须遍历整个集合,提升了检索性能。
高能:
当一个已经存储进HashSet中,就不要修改这个对象存于hashCode运算的字段;因为一旦修改,该对象的哈希值就会改变,那么再去查找或者删除该对象的时候,都无法成功;这有可能造成内存泄漏的情况(Java中内存泄漏的线程)
五、框架的概念即用反射技术开发框架的原理
框架用于调用还未完成编写的Java类
。框架在编写的时候,还不知道它以后所要调用的类的名称,在框架程序中无法直接new
那个类的对象,那么就只能用反射机制来做。
示例代码:
package com.heisejiuhuche.reflec;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.Properties;
public class ReflectReflectionPointDemo {
public static void main(String[] args) throws Exception {
/* 创建输入流对象 */
InputStream is = new FileInputStream("conf.properties");
/* 创建Properties对象 */
Properties props = new Properties();
/* 通过输入流加载properties配置文件 */
props.load(is);
/* 获取配置文件中的className键的值,这个值就是这个框架运行时要调用的类名 */
String className = props.getProperty("className");
/* 得到这个类名的字节码对象并调用其空参数构造方法实例化一个对象
* 将这个对象转换成Collection类型 */
Collection collection = (Collection)Class.forName(className).newInstance();
ReflectionPoint pt1 = new ReflectionPoint(3,3);
ReflectionPoint pt2 = new ReflectionPoint(5,5);
ReflectionPoint pt3 = new ReflectionPoint(3,3);
collection.add(pt1);
collection.add(pt2);
collection.add(pt3);
collection.add(pt1);
System.out.println(collection.size());
}
}
conf.properties配置文件内容:className=java.util.HashSet
程序运行结果:2
六、内省
1.概述
内省(Introspector)
用于对JavaBean
进行操作。JavaBean
是一个特殊的Java类
;JavaBean类
中的方法名要符合某种约定的规则。
2.JavaBean类JavaBean
主要用于传递数据信息。这种Java类
中的方法主要用于访问私有的字段,且方法名符合一定的规则。下面的Person类
示例中,有一个getAge()
和setAge()
方法,那么这个Person类
可以被称作一个JavaBean类
。
class Person {
private int x;
public int getAge() {
return x;
}
public void setAge(int age) {
this.x = age;
}
}
如果一个类是JavaBean类,就可以使用操作JavaBean类的方法来操作这个类。操做这样的JavaBean类有如下两个特点:
1)这个类拥有的成员变量是通过符合特定规则的方法名推断出来的;Person类
中有的成员变量是通过get
和set
方法推断的,即age
成员;
2)在推断JavaBean类
成员的过程中,要去掉get
和set
;剩下的名称在运用的时候,如果名称的第二个字母是小写,则将第一个字母也改成小写
3.内省操作JavaBean
JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。内省提供了更加方便的方式来访问类的私有成员变量。开发中,当要在两个模块之间传递多个信息,通常将这些信息封装在一个JavaBean中。这种JavaBean的实例对象通常被称之为值对象(Value Object
,简称VO
)。这些信息在类中用私有的字段来储存,访问这些信息需要调用相应的方法。下面使用内省的方式来操作JavaBean。
示例代码:
package com.heisejiuhuche.reflec;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
public class ReflectIntrospectorDemo {
public static void main(String[] args) throws Exception {
ReflectionPoint p = new ReflectionPoint(3,5);
/* 通过推断ReflectionPoint类的get和set方法得到其有一个成员x */
String propertyName = "x";
/* 创建PropertyDescriptor对象,关联成员名和类,这个类就是被当成JavaBean的类 */
PropertyDescriptor pd = new PropertyDescriptor(propertyName, ReflectionPoint.class);
/* 调用getReadeMethod方法获取JavaBean类的get方法 */
Method methodGetX = pd.getReadMethod();
System.out.println(methodGetX.invoke(p));
/* 调用getWriteMethod方法获取JavaBean类的set方法 */
Method methodSetX = pd.getWriteMethod();
/* 调用invoke设置x的值 */
methodSetX.invoke(p, 7);
System.out.println(p.getX());
}
}
程序运行结果:
3
7
4.使用BeanInfo操作JavaBeanBeanInfo
封装了JavaBean类
所有的信息,代表了这个JavaBean类
。BeanInfo
可以通过IntroSpector类
调用其静态方法getBeaInfo()
来获取。
示例代码:
package com.heisejiuhuche.reflec;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
public class ReflectIntrospectorDemo {
public static void main(String[] args) throws Exception {
ReflectionPoint p = new ReflectionPoint(3,5);
/* 通过推断ReflectionPoint类的get和set方法得到其有一个成员x */
String propertyName = "x";
/* 通过IntroSpector类的静态方法getBeanInfo得到BeanInfo对象 */
BeanInfo beanInfo = Introspector.getBeanInfo(p.getClass());
/* 调用BeanInfo对象的getPropertyDescriptors方法得到JavaBean类所有成员描述 */
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
/* 遍历数组,如果某个成员的成员名和设定的成员名相同,就的到JavaBean的get方法*/
for(PropertyDescriptor pdz : pds) {
if(pdz.getName().equals(propertyName)) {
Method methodGetXX = pdz.getReadMethod();
System.out.println(methodGetXX.invoke(p));
}
}
}
}
程序运行结果:3
5.使用BeanUtils工具包操作JavaBeanBeanUtils
是Apache提供的操作JavaBean的工具包,使用更加方便,提高开发效率。
使用BeanUtils工具包的好处:
第一个好处,BeanUtils工具包可以自动完成字符串和整数的转换;web开发
中数据显示在网页上都是以字符串的形式,有这样的功能可以提高效率;
第二个好处,BeanUtils工具包可以使符合属性的值一级一级传递下去;代码将做演示
第三个好处,BeanUtils工具包可以实现JavaBean
和Map集合
的互相转换;
第四个好处,BeanUtils工具包可以直接操作Map集合
;
示例代码:
package com.heisejiuhuche.reflec;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
public class ReflectIntrospectorDemo {
public static void main(String[] args) throws Exception {
ReflectionPoint p = new ReflectionPoint(3,5);
/* 使用getProperty方法可以获取JavaBean类的指定属性,这个方法返回的是String类型*/
System.out.println("getProperty name: " + BeanUtils.getProperty(p, "x").getClass().getName());
/* 通过setProperty方法可以设置JavaBean类的指定属性,属性的值可以是属性对应的类型
* 也可以是字符串 */
BeanUtils.setProperty(p, "x", "9"/*9*/);
System.out.println("BeanUtils set x: " + BeanUtils.getProperty(p, "x"));
/* PropertyUtils类也有setProperty方法 ,属性的值只能是属性对应的类型*/
PropertyUtils.setProperty(p, "x", 100);
System.out.println("PropertyUtils set x: " + PropertyUtils.getProperty(p, "x"));
/* 在ReflectionPoint类中定义了一个Date类型的变量date,然后为其设置了get和set方法
* 通过api文档,Date类有setTime方法,那么Date类也可以被看作是一个JavaBean
* 所以,这个date变量,是一个双重属性,他在ReflectionPoint这个JavaBean中,又属于
* Date这个JavaBean;那么,Date类中的属性,也可以通过JavaBean的方式来操作,只需要
* 通过date对象调用省去set之后的属性(setTime方法按照JavaBean的规则,省去set,将T
* 变为t,那么这个属性就应该是time),如: date.time
*/
BeanUtils.setProperty(p, "date.time", "1991");
System.out.println("date: " + BeanUtils.getProperty(p, "date.time"));
/* 定义一个Map 用于存储JavaBean */
Map map = new HashMap();
/* 调用BeanUtils的describe方法,将JavaBean转换成map */
map = BeanUtils.describe(p);
System.out.println("JavaBean to Map: " + map);
map.clear();
/* 在空map中添加一个键值对 */
map.put("a", "1");
/* 通过setProperty方法对map进行操作 修改值 */
BeanUtils.setProperty(map, "a", "JavaBean");
System.out.println("setProperty map: " + map);
}
}
程序运行结果:
getProperty name: java.lang.String
BeanUtils set x: 9
PropertyUtils set x: 100
date: 1991
JavaBean to Map: {date=Thu Jan 01 08:00:01 CST 1970, x=100, y=5, class=class com.heisejiuhuche.reflec.ReflectionPoint}
setProperty map: {a=JavaBean}
注意:
BeanUtils工具包可以以属性对应类型或字符串的形式对JavaBean进行操作;而PropertyUtils只能以属性本身的类型对JavaBean进行操作