------- android培训、java培训、期待与您交流! ----------
反射的应用
反射就是把java类中的各种成分映射成相应的java类。首先讨论下昨天遇到的问题,
/*
* 对两个使用了泛型的集合操作,出现下面问题
*/
public static void main(String[] args) throws Exception {
//通过反射向Integer集合中添加String可以正常取出打印
ArrayList<Integer> arr1 = new ArrayList<Integer>();
arr1.getClass().getMethod("add", Object.class).invoke(arr1, "abc");
System.out.println(arr1.get(0));
//通过反射向String集合中添加Integer,在打印时却出现类型转换异常
//这是什么原因?
ArrayList<String> arr2 = new ArrayList<String>();
arr2.getClass().getMethod("add", Object.class).invoke(arr2, 2);
System.out.println(arr2.get(0));
}
泛型信息在生成class文件时被擦除,通过反射就可以操作原来不允许的类型。表面看来是没有问题的。
但是我们却忽略了编译时根据泛型String会静态绑定println(String str)方法,但是在运行时却取出了Integer类型,自然会出现类型转换异常。
如果泛型不是String,编译时会调用println(Object obj)方法,运行时再根据obj的实际类型动态绑定调用相应的toString方法,动态绑定实际上是运行时绑定,在运行时根据参数类型决定调用合适的方法。但是当在编译期可以明确的,就会静态绑定某个方法。
下面再来学习一些反射的应用:
Class类中有一个方法:
class.isPrimitive()判断Class对象是不是基本数据类型九个预定义Class实例对象:8中基本数据类型和void.class。这些类对象由 Java 虚拟机创建
int.class == Integer.class;//false
Int.class == Integer.TYPE//true
JDK升级可变参数和数组的问题
问题产生:用反射的方式根据用户提供的类名,去执行该类中的main方法。问题:
启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),
按jdk1.5的语法,整个数组被当成是一个参数,
按jdk1.4的语法,数组中的每个元素对应一个参数,
当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?
jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。
所以,在给main方法传递参数时,使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题
//TestArguments.main(new String[]{"111","222","333"});
String startingClassName = args[0];
Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);
// invoke(obj, obj...args)invoke方法在JDK1.5的参数变为可变参数
//下面是两种可行的解决方法,将数组包装数组的元素或者明确字符串数组参数是一个对象
//mainMethod.invoke(null, new Object[]{new String[]{"111","222","333"}});
mainMethod.invoke(null, (Object)new String[]{"111","222","333"});
反射的应用--->实现框架功能
框架和工具类的区别,工具类被用户的类调用,框架则是调用用户的类。通过代码来学习:获取配置文件:
public static void main(String[] args) throws Exception{
Properties props = new Properties();
//使用相对路径后去配置文件,移植性不好
//InputStream ips = new FileInputStream("config.properties");
/*一个类加载器能加载.class文件,那它当然也能加载classpath环境下的其他文件,
注意:直接使用类加载器时,文件名不能以/打头。*/
//InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/javaenhance/config.properties");
//Class提供了一个便利方法,用加载当前类的那个类加载器去加载相同包目录下的文件
//InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");
//如果使用class类加载器加载文件时,文件名以/开头,表示绝对路径,从classpath下寻找文件
InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/javaenhance/config.properties");
props.load(ips);
Ips.close();
String className = props.getProperty("className");
Class clazz = Class.forName(className);
//已经知道配置文件使用的是Collection的子类,通过反射创建对象
Collection collection = (Collection)clazz.newInstance();
//Collection collection = new ArrayList();
ReflectPoint pt1 = new ReflectPoint(3,3);
ReflectPoint pt2 = new ReflectPoint(5,5);
ReflectPoint pt3 = new ReflectPoint(3,3);
collection.add(pt1);
collection.add(pt2);
collection.add(pt3);
collection.add(pt1);
System.out.println(collection.size());
}
JavaBean
JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。
JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。
如果方法名为setId,中文意思即为设置id,
如果方法名为getId,中文意思即为获取id,
去掉前缀,剩余部分就是属性名。
java.Beans包 API用于对JavaBean进行操作。
PropertyDescriptor 类,用于对JavaBean属性进行操作public PropertyDescriptor(String propertyName,Class<?> beanClass) 构造方法
Method getReadMethod() 获得读取属性值的方法。
Method getWriteMethod() 获得写入属性值的方法。
BeanInfo 接口---->SimpleBeanInfo 空语句实现类
PropertyDescriptor[] getPropertyDescriptors() 获得 beans PropertyDescriptor。
IntroSpector 类
static BeanInfo getBeanInfo(Class<?> beanClass) 在 Java Bean 上进行内省,了解其所有属性、公开的方法和事件。
通过代码学习内省
public class IntroSpectorTest {
/**
* @param args
*/
public static void main(String[] args) throws Exception {
// 创建一个JavaBean对象pt1
ReflectPoint pt1 = new ReflectPoint(3,5);
String propertyName = "x";
//"x"-->"X"-->"getX"-->MethodGetX-->
//抽取获取属性值的方法,通过内省返回pt1对象的propertyName属性的值
Object retVal = getProperty(pt1, propertyName);
System.out.println(retVal);
Object value = 7;
//抽取设置属性值的方法,通过内省设置pt1对象的propertyName属性的值为value
setProperties(pt1, propertyName, value);
//BeanUtils工具包,BeanUtils类get属性返回String字符串
System.out.println(BeanUtils.getProperty(pt1, "x").getClass().getName());
//BeanUtils类set属性时可以接受任意类型的对象,通常使用字符串
BeanUtils.setProperty(pt1, "x", "9");
System.out.println(pt1.getX());
//java7的新特性,Map的初始化方式
// Map map = {name:"zxx",age:18};
// BeanUtils.setProperty(map, "name", "lhm");
//在JavaBean类ReflectPoint中定义一个Date字段,可以直接通过字段设置Date的time属性
BeanUtils.setProperty(pt1, "birthday.time", "111");
System.out.println(BeanUtils.getProperty(pt1, "birthday.time"));
//PropertyUtils的get属性时返回的结果为该属性本来的类型,set属性时只接受该属性本来的类型
PropertyUtils.setProperty(pt1, "x", 9);
System.out.println(PropertyUtils.getProperty(pt1, "x").getClass().getName());
}
private static void setProperties(Object pt1, String propertyName,
Object value) throws Exception {
PropertyDescriptor pd2 = new PropertyDescriptor(propertyName,pt1.getClass());
Method methodSetX = pd2.getWriteMethod();
methodSetX.invoke(pt1,value);
}
private static Object getProperty(Object pt1, String propertyName)
throws Exception {
//使用PropertyDescriptor获取属性的方法
/*PropertyDescriptor pd = new PropertyDescriptor(propertyName,pt1.getClass());
Method methodGetX = pd.getReadMethod();
Object retVal = methodGetX.invoke(pt1);*/
//BeanInfo接口,通过内省获取JavaBean的信息
BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass());
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
Object retVal = null;
for(PropertyDescriptor pd : pds){
if(pd.getName().equals(propertyName))
{
Method methodGetX = pd.getReadMethod();
retVal = methodGetX.invoke(pt1);
break;
}
}
return retVal;
}
}
apache BeanUtils工具包 需要logging包
添加jar包,设置buildpath
采用BeanUtils去获取带有抽象方法的枚举类的成员对象的属性时,会出现错误,要自己用内省加暴力反射方式才可以获取。主要原因是枚举类的抽象子类不是public类型的。