一、反射:一个Java类中,包含成员变量、成员方法等信息。反射就是把类中的这些信息映射成一个个的类。
反射的基本使用:
获取类的字节码对象有三种方法:
- 类名.class
- 对象名.getClass()
- Class.forName(类的全名)
Class对象提供了如下常用方法:
- public Constructor getConstructor(Class<?>...parameterTypes)
- public Method getMethod(String name, Class<?>...parameterTypes)
- public Field getField(String name)
- public Constructor getDeclaredConstructor(Class<?>...parameterTypes)
- public Method getDeclaredMethod(String name, Class<?>...parameterTypes)
- public Field getDeclaredField(String name)
Student类
public class Student {
public String str = "hello";
private int age = 18;
public static Date time;
public Student() {
}
public Student(String name){
System.out.println("姓名:" + name);
}
public Student(String name, int age){
System.out.println(name + " " + age);
}
private Student(int age){
System.out.println("年龄:" + age);
}
public void m1(){
System.out.println("m1");
}
public void m2(String name){
System.out.println("m2 " + name);
}
public String m3(String name, int age){
System.out.println("m3 " + name + " " + age);
return "m3:OK";
}
private void m4(int age){
System.out.println("m4 " + age);
}
public static void m5(){
System.out.println("m5");
}
private static void m6(String[] str){
System.out.println(str.length);
}
}
public class Reflect {
public static void main(String[] args) throws Exception {
Class cl = Class.forName("com.ztq.test.Student"); //获取Student类的字节码对象
Student s = (Student) cl.newInstance(); //利用字节码对象cl,建立Student类的实例,调用的是默认构造方法
Constructor c1 = cl.getConstructor(String.class); //获取相应的构造方法(只能是public修饰的)
c1.newInstance("Chris"); //创建新实例
Constructor c2 = cl.getDeclaredConstructor(int.class); //获取相应的构造方法(所有的,包括private修饰的)
c2.setAccessible(true);
c2.newInstance(22);
Constructor[] c3 = cl.getDeclaredConstructors(); //获取所有构造方法(包括private)
Method m1 = cl.getMethod("m1", null); //获取名为m1,参数为null的方法
m1.invoke(s, null); //调用该方法,其中s为该类的一个实例,null为该方法的参数
Method m2 = cl.getMethod("m2", String.class);
m2.invoke(s, "Chris");
Method m3 = cl.getMethod("m3", String.class, int.class);
String str = (String) m3.invoke(s, "Chris", 18);
Method m4 = cl.getDeclaredMethod("m4", int.class);
m4.setAccessible(true);
m4.invoke(s, 20);
Method m5 = cl.getMethod("m5", null);
m5.invoke(s, null);
// m5.invoke(null, null); 对于静态方法,对象可以为s,也可以为null
Method m6 = cl.getDeclaredMethod("m6", String[].class);
m6.setAccessible(true);
m6.invoke(null, (Object)new String[]{"a", "b", "c"});
Field f1 = cl.getField("str");
f1.set(s, "hehe"); //更改类中的str字段
Field f2 = cl.getDeclaredField("age");
f2.setAccessible(true);
f2.set(s, 30);
Field f3 = cl.getField("time");
f3.set(null, new Date());
}
}
参数中带有数组的,如方法“m6”,要将数组转换成一个对象。这是因为,jdk1.5及以上版本要兼容1.4。在1.4中,数组中的每个元素都对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,会出现参数数量错误。解决方法:
invoke(null, new Object[]{new String[]{"xxx", "xx"}});
或者
invoke(null, (Object)new String[]{"xxx", "xx"});
这样编译器会做特殊处理,编译时不会把参数当做数组看待。
二、内省:
WIKI上的解释:内省是指计算机程序在运行时(Run time)检查对象(Object)类型的一种能力,通常也可以称作运行时类型检查。
相对于内省,反射更进一步,是指计算机程序在运行时(Run time)可以访问、检查和修改它本身状态或行为的一种能力。
内省和反射的区别:
反射是在运行状态把Java类中的各种成分映射成相应的Java类,可以动态的获取所有的属性以及动态调用任意一个方法,强调的是运行状态。
内省机制是通过反射来实现的,BeanInfo用来暴露一个bean的属性、方法和事件,以后我们就可以操纵该JavaBean的属性了。
在Java内省中,用到的基本就是上述几个类。通过BeanInfo这个类就可以获取到类中的方法和属性。
举例:
Person类
public class Person {
private String name = "Tom";
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void eat(){
System.out.println("eat food!");
}
}
public class Reflect {
public static void main(String[] args) throws Exception {
Person p = new Person();
//获取Person类中的属性,被封装到了BeanInfo中,若想获得(不包括Object类中的)属性,则参数为(Person.class, Object, class)
BeanInfo bi = Introspector.getBeanInfo(Person.class);
PropertyDescriptor[] pds = bi.getPropertyDescriptors(); //获得类中所有属性描述器
System.out.println("属性描述器个数:" + pds.length); //3个,因为Object类中的getClass()方法,也算作1个属性
for(PropertyDescriptor pd : pds){
System.out.println(pd.getName());
}
PropertyDescriptor pd = new PropertyDescriptor("name", Person.class);
Method m = pd.getReadMethod(); //获取getName()方法,即读方法
String value = (String)m.invoke(p, null);
System.out.println(value);
Method m1 = pd.getWriteMethod();
m1.invoke(p, "Jerry");
System.out.println(p.getName());
}
}
输出:
属性描述器个数:3
age
class
name
Tom
Jerry
setName
三、BeanUtils工具包是Apache开发的一套快速操作JavaBean get/set方法的API,需要 commons-beanutils.jar, commons-logging.jar
注:BeanUtils可以进行类型的自动转换,但仅限基本类型
举例:
在原有Person类中加入private Date birthday;,以及get/set方法
public class Reflect {
public static void main(String[] args) throws Exception {
Person p = new Person();
String name = BeanUtils.getProperty(p, "name"); //调用getName()方法
System.out.println(name);
BeanUtils.setProperty(p, "name", "Jerry");
System.out.println(p.getName());
//非基本类型的属性设置,给BeanUtils注册类型转换器
ConvertUtils.register(new Converter() {
//type目标类型
//value当前传入的值
@Override
public Object convert(Class type, Object value) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
if (value instanceof String) {
String v = (String) value;
Date d = null;
try {
d = df.parse(v);
} catch (ParseException e) {
e.printStackTrace();
}
return d;
} else {
Date d = (Date)value;
return df.format(d);
}
}
}, Date.class);
BeanUtils.setProperty(p, "birthday", "2017-10-10");
System.out.println(p.getBirthday());
/*
也可以采取这种方式
ConvertUtils.register(new DateLocaleConverter(), Date.class); //DateLocaleConverter是对上面代码的封装
BeanUtils.setProperty(p, "birthday", "1999-09-09");
System.out.println(p.getBirthday());
*/
}
}
BeanUtils可以将Map属性自动放到Bean中
注:原则是,Map的key必须要与Bean的属性一致
举例:
public class Reflect {
public static void main(String[] args) throws Exception {
HashMap map = new HashMap();
map.put("name", "zhang");
Person p = new Person();
BeanUtils.populate(p, map);
System.out.println(p.getName());
}
}