反射:就是把Java类中的各种成分映射成一个个的Java对象。例如,一个类有:成员变量,成员方法,包等信息,利用反射技术可以对一个类进行解剖,把各个部分映射成一个个对象。
1. 先得到类的字节码对象:Class cl = Class.forName("类的全名");
或者:Class cl = 类名.class;
或者:Class cl = 类的对象.getClass();
2. 解剖类:
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)
这些方法分别用于从类中解剖出构造函数、方法和成员变量(属性)。解剖出的成员分别使用Constructor、Method、Field对象表示。
先写一个简单的类
1 package javatext; 2 3 import java.util.Date; 4 5 public class Student { 6 public String str = "hello"; 7 8 private int age = 18; 9 10 public static Date time; 11 12 public Student(){} 13 14 public Student(String name){ 15 System.out.println("姓名:" + name); 16 } 17 18 public Student(String name, int age){ 19 System.out.println(name + " " + age); 20 } 21 22 private Student(int age){ 23 System.out.println("年龄:" + age); 24 } 25 26 public void m1(){ 27 System.out.println("m1"); 28 } 29 30 public void m2(String name){ 31 System.out.println("m2 " + name); 32 } 33 34 public String m3(String name, int age){ 35 System.out.println("m3 " + name + " " + age); 36 return "m3:OK"; 37 } 38 39 private void m4(int age){ 40 System.out.println("m4 " + age); 41 } 42 43 public static void m5(){ 44 System.out.println("m5"); 45 } 46 47 private static void m6(String[] str){ 48 System.out.println(str.length); 49 } 50 }
反射
1 package javatext; 2 3 import java.lang.reflect.Constructor; 4 import java.lang.reflect.Field; 5 import java.lang.reflect.Method; 6 import java.util.Date; 7 8 public class _1{ 9 10 public static void main(String[] args)throws Exception{ 11 Class cl = Class.forName("javatext.Student"); //Student类在内存中的字节码对象 12 13 Student s = (Student)cl.newInstance(); //可以直接用类的字节码对象,建立该类新的实例,调用的是默认的构造方法 14 System.out.println(s.str); 15 16 Constructor c = cl.getConstructor(String.class); //获取相应的构造方法,只能是public的方法 17 c.newInstance("Chris"); //创建新实例 18 19 Constructor cc = cl.getDeclaredConstructor(int.class); //获取相应的构造方法(所有,包括私有构造方法) 20 cc.setAccessible(true); 21 cc.newInstance(22); 22 23 Constructor[] ccc = cl.getDeclaredConstructors(); //获取所有的构造方法(包括私有) 24 System.out.println(ccc.length); 25 26 Method m1 = cl.getMethod("m1", null); //获取名为m1,参数为null的方法 27 m1.invoke(s, null); //调用该方法,s为该类的一个实例,null为该方法的参数 28 29 Method m2 = cl.getMethod("m2", String.class); 30 m2.invoke(s, "Chris"); 31 32 Method m3 = cl.getMethod("m3", String.class, int.class); 33 String str = (String)m3.invoke(s, "Chris", 22); 34 System.out.println(str); 35 36 Method m4 = cl.getDeclaredMethod("m4", int.class); 37 m4.setAccessible(true); 38 m4.invoke(s, 22); 39 40 Method m5 = cl.getMethod("m5", null); 41 m5.invoke(null, null); //对于静态方法,对象可以为s,也可以为null 42 43 Method m6 = cl.getDeclaredMethod("m6", String[].class); 44 m6.setAccessible(true); 45 m6.invoke(null, (Object)new String[]{"a", "b", "c"}); //参数中带有数组的,要将其转换成一个对象 46 //注:由于要向下兼容,jdk1.5及以上版本要兼容jdk1.4。在1.4中,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给 47 //invoke方法时,会出现参数数量错误的问题。 48 //解决办法:invoke(null, new Object[]{new String[]{"xxx", "xx"}}); 49 // 或者: invoke(null, (Object)new String[]{"XX", "XXX"}); 50 //这样编译器会做特殊处理,编译时不把参数当作数组看待,也就不会将数组打散成若干参数了。 51 52 Field f1 = cl.getField("str"); 53 String string = (String)f1.get(s); 54 System.out.println(string); 55 56 f1.set(s, "hehe"); //更改类中的str字段 57 System.out.println(s.str); 58 59 Field f2 = cl.getDeclaredField("age"); 60 f2.setAccessible(true); 61 int age = (Integer)f2.get(s); 62 System.out.println(age); 63 64 f2.set(s, 80); 65 age = (Integer)f2.get(s); 66 System.out.println(age); 67 68 Field f3 = cl.getField("time"); 69 f3.set(null, new Date()); 70 System.out.println(Student.time); 71 } 72 }
结果:
hello 姓名:Chris 年龄:22 4 m1 m2 Chris m3 Chris 22 m3:OK m4 22 m5 3 hello hehe 18 80 Sat Sep 17 20:38:34 GMT+08:00 2016
内省:开发框架时,经常需要使用java对象的属性来封装程序的数据,每次都使用反射技术完成此类操作过于麻烦,所以Sun公司开发了一套API,专门用于操作java对象的属性。
通过内省技术访问(java.beans包提供了内省的API)JavaBean的两种方式。
1. 通过Introspector类获得Bean对象的BeanInfo,然后通过BeanInfo来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的getter/setter方法,然后通过反射机制来调用这些方法。
2. 通过PropertyDescriptor类操作Bean的属性。
写一个简单类:
1 package javatext; 2 3 public class Person { 4 private String name = "Chris"; 5 private int age; 6 public String getName() { 7 return name; 8 } 9 public void setName(String name) { 10 this.name = name; 11 } 12 public int getAge() { 13 return age; 14 } 15 public void setAge(int age) { 16 this.age = age; 17 } 18 }
内省:
package javatext; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; public class _1{ public static void main(String[] args)throws Exception{ Person p = new Person(); //获取所有属性 BeanInfo bi = Introspector.getBeanInfo(Person.class);//获取Person类中的属性,被封装到了BeanInfo中,若想获得(不包括Object类中的)属性,则参数为(Person.class, Object.class) PropertyDescriptor[] pds = bi.getPropertyDescriptors();//获取类中的所有的属性描述器 System.out.println(pds.length); //显示3个属性,以为Object类中有getClass()方法,也算作1个属性 for(PropertyDescriptor pd : pds){ System.out.println(pd.getName()); //打印属性名称,即去掉get、set的名称 } //操作Bean的指定属性 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(); //获取setName()方法,即写方法 m1.invoke(p, "Lily"); System.out.println(p.getName()); } }
结果:
3 age class name Chris Lily
内省--beanutils工具包:
Beanutils是Apache开发的一套快速操作JavaBean getter/setter方法的API,目前比较流行。
准备包:commons-beanutils.jar, commons-logging.jar
语法:
(1)设置:BeanUtils.setProperty(Object bean, String propertyName, String propertyValue);
(2)获取:BeanUtils.getProperty(Object bean, String PropertyName);
注:BeanUtils可以进行类型的自动转换,但仅限基本类型。
继续用上面的Person类,额外添加了birthday属性。
1 package javatext; 2 3 import java.text.DateFormat; 4 import java.text.SimpleDateFormat; 5 import java.util.Date; 6 7 import org.apache.commons.beanutils.BeanUtils; 8 import org.apache.commons.beanutils.ConvertUtils; 9 import org.apache.commons.beanutils.Converter; 10 import org.apache.commons.beanutils.locale.converters.DateLocaleConverter; 11 12 public class _1{ 13 public static void main(String[] args)throws Exception{ 14 Person p = new Person(); 15 String str = BeanUtils.getProperty(p, "name"); //调用getName()方法 16 System.out.println(str); 17 18 BeanUtils.setProperty(p, "name", "Lily"); //设置值 19 System.out.println(p.getName()); 20 21 //非基本类型的属性设置 22 //给BeanUtils注册类型转换器 23 ConvertUtils.register(new Converter(){ 24 //type:目标类型 25 //value:当前传入的值 26 public Object convert(Class type, Object value){ 27 // if(type.equals(Date.class)){ 28 // //字符串转换为Date 29 // }else{ 30 // //Date转换为字符串 31 // } 32 DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); 33 if(value instanceof String){ 34 //字符串转换为Date 35 String v = (String)value; 36 try{ 37 Date d = df.parse(v); 38 return d; 39 }catch(Exception e){ 40 throw new RuntimeException(e); 41 } 42 }else{ 43 //Date转换为字符串 44 Date d = (Date)value; 45 return df.format(d); 46 } 47 } 48 }, Date.class); 49 BeanUtils.setProperty(p, "birthday", "2016-09-17"); 50 System.out.println(p.getBirthday());
结果:
Chris
Lily
Sat Sep 17 00:00:00 GMT+08:00 2016
或者
1 ConvertUtils.register(new DateLocaleConverter(), Date.class); 2 BeanUtils.setProperty(p, "birthday", "1999-09-09"); 3 System.out.println(p.getBirthday());
注:其中的DateLocalConverter()就是对上面代码的封装。
BeanUtils将Map属性自动放到Bean中
注:可以操作的原则是,Map的key必须要与Bean的属性一致。
1 Map m = new HashMap(); 2 m.put("name", "Chris"); 3 Person p = new Person(); 4 BeanUtils.populate(p, m); 5 System.out.println(person);