一、 分析反射的技术 Class类
反射的基石:Class类
Java类属于同一类事物,描述这类事物的Java类名就是Class。
例如:
人->Person
Java类->Class
Person类代表人,其实例对象就是张三、李四这种一个个具体的人,Class类代表Java类,它的每个实例对象分别对应每个类在内存中的字节码,例如:Person类的字节码、String类的字节码等。当一个类被类加载到内存中占用一段存储空间,这个空间里面的内容就是类的字节码,不同类的字节码是不同的,所以它们在内存中的内容也是不同的,这一个个的空间可以分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?
怎么得到各个字节码对应的实例对象(Class类型)
1. 类名.class 例如:System.class
2. 对象.getClass() 例如:new Date().getClass()
3. Class.forName("类名") 例如:Class.forName("java.util.Date")
它的作用:返回字节码,返回的方式:
1. 字节码曾经被加载过,已经在虚拟机中,直接返回
2. 没有给加载过,则使用类加载器加载,缓存到虚拟机中,以后就要得到字节码就不用加载了。
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
例:int.class!=Integer.TYPE (后者为前者的包装类,当然类对应的字节码不一样)
数组类型的Class实例对象:
Class.isArray()
总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如int[] 、void
实例:
package cn.cast.day1;
public class ReflectTest {
public static void main(String[] args)throws Exception {
String str="yangcheng";
Class cls1=str.getClass();
Class cls2=String.class;
Class cls3=Class.forName("java.lang.String");//返回与带有给定字符串名的类或接口相关联的 Class 对象。
System.out.println(cls1==cls2);
System.out.println(cls1==cls3);
System.out.println(cls1.isPrimitive());//判定指定的 Class 对象是否表示一个基本类型。
System.out.println(int.class.isPrimitive());
System.out.println(int.class==Integer.class);
System.out.println(int.class==Integer.TYPE);
System.out.println(int[].class.isPrimitive());
}
}
结果:
true
true
false
true
false
true
false
二、 反射的概念
反射就是把Java类中的各个成分映射成相应的java类。例如:一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量、构造方法、成员方法、包等信息也用一个个Java类来表示。
对应关系:
成员变量:Field
成员方法:Method
构造方法:Contructor
包:Package
......
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,具体的使用在后面有详解。
三、 构造方法的反射应用
Contructor类
这个类代表某个类的一个构造方法。
1. 得到某个类所有的构造方法:
Constructor[] constructors=Class.forName("java.lang.String").getConstructors();
2. 得到某一个构造方法:
Constructor constructor=Class.forName("java.lang.String").getConstructor(StringBuffer.class);
3. 创建实例对象:
通常方法:String str=new String(new StringBuffer("abc"));
反射方法:String str=(String)constructor.newInstance(new StringBuffer("abc"));
4. Class.newInstance()方法:
String obj=(String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部的具体代码用到了缓存机制来保存默认构造方法的实例对象。
四、 成员变量的反射
Field类
这个类代表某个类中的一个成员变量
实例:
package cn.cast.day1;
public class ReflectPoint {
private int x;
public int y;
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
主函数代码段:
ReflectPoint pt1=new ReflectPoint(3,5);
Field fieldY=pt1.getClass().getField("y");//返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
System.out.println(fieldY.get(pt1));
Field fieldX=pt1.getClass().getDeclaredField("x");//返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
fieldX.setAccessible(true);
/*
public final class Field extends AccessibleObject implements Member
使用单一安全性检查(为了提高效率)为一组对象设置 accessible 标志的便捷方法。 true:“暴力”访问*/
System.out.println(fieldX.get(pt1));
结果:
5
3
所以,得到的Field对象是对应到类上面的成员变量,字段fieldX代表的是x的定义,而不是具体的x变量。
五、 成员变量反射的综合案例
将任意一个对象中的虽有String类型的成员变量所对应的字符串内容中的"b"改成"a"
private static void changeStringValue(Object obj)throws Exception{
Field[] fields=obj.getClass().getFields();
for(Field field:fields){
if(field.getType()==String.class){
String oldValue=(String)field.get(obj);
String newValue=oldValue.replace('b', 'a');
field.set(obj, newValue);
}
}
}
六、 成员方法的反射
Method类
这个类代表谋个类中的一个成员方法
得到某一个方法:
Method methodCharAt=String.class.getMethod("charAt", int.class);
调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式:System.out.println(methodCharAt.invoke(str, 1));
JDK1.4和JDK1.5的invoke方法的区别:
JDK1.5:public Object invoke(Object obj,Object ... args)
JDK1.4:public Object invoke(Object obj,Object[] args),按JDK1.4的语法需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用JDK1.4改写为charAt.invoke("str",new Object[] {1})的形式。
例如:
String str1="abc";
Method methodCharAt=String.class.getMethod("charAt", int.class);
System.out.println(methodCharAt.invoke(str1, 1));
System.out.println(methodCharAt.invoke(str1, new Object[] {2}));
//new Object[] {2} 有自动装箱的机制。
结果:
b
c
七、 对接收数组参数的成员方法进行反射
写一个程序,这个程序能根据用户提供的类名,去执行该类中的main方法。
class TestArguments{
public static void main(String[] args){
for(String arg: args){
System.out.println(arg);
}
}
}
主函数中:
String startingClassName=args[0];
Method mainMethod=Class.forName(startingClassName).getMethod("main", String[].class);
mainMethod.invoke(null,new Object[]{new String[]{"abc","def","ddd"}});
按右键在Run As->Open Run Dialog中,设置Arguments->Program argumens:
cn.cast.day1.TestArguments
以便程序在一运行,就往ReflectTest类main函数的参数传递cn.cast.day1.TestArguments
问题:
启动Java程序的main方法的参数是一个字符串数组,String[] args ,通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?参照JDK1.5语法,整个数组是一个参数,而按JDK1.4语法,数组中的每一个元素对应一个参数,当把一个字符串数组最为参数传递给invoke方法时,javac会按照哪种语法进行处理呢?JDK1.5肯定要兼容JDK1.4的语法,那么会按JDK1.4的语法进行处理,也就是把数组拆散成若干个单独的参数,所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[] {"xxx"}),javac只把它当作JDK1.4的语法进行理解,而不把它当作JDK1.5的语法理解,因此会出现参数类型不正确的问题。
解决办法:
mainMethod.invoke(null,new Object[]{new String[] {"xxx"}})
mainMethod.invoke(null,(Object)new String[] {"xxx"}) 编译器会进行特殊的处理,编译时不把参数当作数组看待,也就不会将数组拆散成若干个参数。
八、 数组与Object的关系及其反射类型
数组的反射
具有相同维数和元素类型的数组属于同一个类型,也就是具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
基本类型的一维数组可以被看作Objecet类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以看做Object类型使用,又可以当作Object[]类型使用。
例子:
int[] a1=new int[]{1,2,3};
int[] a2=new int[4];
int[][] a3=new int[2][3];
String[] a4=new String[]{"a","b","c"};
System.out.println(a1.getClass()==a2.getClass());
System.out.println(a1.getClass()==a4.getClass());
System.out.println(a1.getClass()==a3.getClass());
System.out.println(a1.getClass().getName());
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(a4.getClass().getSuperclass().getName());
Object obj1=a1;
Object obj2=a4;
Object[] obj4=a3;
Object[] obj5=a4;
System.out.println(a1);
System.out.println(a4);
System.out.println(Arrays.asList(a1)); //适配JDK1.5的asList方法。
System.out.println(Arrays.asList(a4)); //将数组转换为List类型对象 适配JDK1.4的asList方法。
结果:
true
false
false
[I
java.lang.Object
java.lang.Object
[I@110003
[Ljava.lang.String;@17e4ca
[[I@110003]
[a, b, c]
九、 数组的反射应用
用Array工具类完成对数组的反射操作:
例:
private static void printObject(Object obj){
Class cls=obj.getClass();
if(cls.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);
}
}
主函数程序段:
String[] a4=new String[]{"a","b","c"};
printObject(a4);
printObject("abc");
结果:
a
b
c
abc
怎么得到数组中的元素类型?
Int a[]=new a[]{1,2,3};
Object[] a=new Object[]{"abc",1};
a.getClass().getName();
只能通过数组中的某一个元素得到这个元素的类型,但不能通过某个元素得到整个数组的元素类型。
十、 框架的概念及用反射技术开发框架的原理
框架
我做房子卖给户主,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
框架要解决的核心问题
我在写框架时,用户可能还不会写程序,我写的框架程序怎样去调用后来用户写的类呢?
因为在写程序是无法知道要被调用的类名,所以在程序中无法直接new某个类的实例对象,而要用反射来做。
例子:
往工程新建一个config.properties配置文件,写入内容:
className=java.util.ArrayList
package cn.cast.day1;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.Properties;
public class ReflectTest2 {
public static void main(String[] args)throws Exception{
InputStream ips=new FileInputStream("config.properties");
Properties pros=new Properties();
pros.load(ips);
ips.close();
String className=pros.getProperty("className");
Collection collections=(Collection)Class.forName(className).newInstance();
//Collection collections=new HashSet();
String st1=new String("123");
String st2=new String("456");
String st3=new String("123");
collections.add(st1);
collections.add(st2);
collections.add(st3);
collections.add(st1);
System.out.println(collections.size());
}
}
结果:
4
现在更改config.properties配置文件,写入内容:
className=java.util.HashSet
结果:
2
十一、 用类加载器的方式管理资源和配置文件
默认将配置文件config.properties放在cn/cast/day1/
注意:MyEclipse会将src下的资源文件按文档结构也复制到bin目录下去。
方式1:
InputStream ips=ReflectTest2.class.getClassLoader().getResourceAsStream("cn/cast/day1/config.properties");
方式2:
InputStream ips=ReflectTest2.class.getResourceAsStream("config.properties");
将配置文件放在一个src新建的cn.cast.day1.resource目录下
则:
InputStream ips=ReflectTest2.class.getResourceAsStream("resource/config.properties");
或:
InputStream ips=ReflectTest2.class.getResourceAsStream("/cn/cast/day1/resource/config.properties");
十二、 由内省引出JavaBean的讲解
内省:IntroSpector
JDK中提供了对JavaBean进行操作的一些API函数,这套API就称为内省。
class Person{
private String name;
public String setName(String name){
this.name=name;
}
public void getName(){
return name;
}
}
getName---->name
setName---->name
JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。
如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象或设置这些字段的值,则需要通过一些相应的方法来访问,那这些方法叫什么名字呢?JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,是设置Id的意思,至于你把它存到哪个变量上,不用管。如果方法名是getId,是获取Id的意思,至于在哪个变量获取不用管。去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写,则把剩余部分的首字母改成小写。
setId()的属性名:id
isLast()的属性名:last
setCPU的属性名:CPU
getUPS的属性名:UPS
总之一个类被当作JavaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。
一个符合JavaBean特点的类可以当作普通类使用,但把它当作JavaBean使用肯定需要带来一定的好处,好处如下:
在Java EE开发中,经常要使用JavaBean,很多环境就要求按JavaBean方式来操作。
如果要自己去通过getX方法来访问私有x,有一定难度。用内省这套API操作JavaBean比用普通类的方式更方便。
十三、 对JavaBean的简单和复杂内省操作
简单:
package cn.cast.day1;
public class ReflectPoint {
private int x;
private int y;
public ReflectPoint(int x,int y){
this.x=x;
this.y=y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
package cn.cast.day1;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class IntroSpectorTest {
public static void main(String[] args)throws Exception{
ReflectPoint pt1=new ReflectPoint(1,3);
String propertyName="x";
Object retVal = getProperty(pt1, propertyName);
System.out.println(retVal);
Object value=10;
setProperty(pt1, propertyName, value);
System.out.println(pt1.getX());
}
private static void setProperty(ReflectPoint pt1, String propertyName,
Object value) throws IntrospectionException,
IllegalAccessException, InvocationTargetException {
//PropertyDescriptor:属性描述
PropertyDescriptor pd2=new PropertyDescriptor(propertyName,pt1.getClass());
Method methodSetX=pd2.getWriteMethod();
methodSetX.invoke(pt1,value);
}
private static Object getProperty(ReflectPoint pt1, String propertyName)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
//PropertyDescriptor:属性描述
PropertyDescriptor pd=new PropertyDescriptor(propertyName,pt1.getClass());
Method methodGetX=pd.getReadMethod();
Object retVal=methodGetX.invoke(pt1);
return retVal;
}
}
结果:
1
10
复杂的操作方式:
采用遍历BeanInfo的所有属性方法来查找和设置某个RefectPoint对象的x属性。在程序中把一个类当作JavaBean来看,就是调用IntroSprctor.getBeanInfo方法,得到的BeanInfo对象封装了把这个类当作JavaBean看的结果信息。
private static Object getProperty(ReflectPoint pt1, String propertyName)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
//PropertyDescriptor:属性描述
/*PropertyDescriptor pd=new PropertyDescriptor(propertyName,pt1.getClass());
Method methodGetX=pd.getReadMethod();
Object retVal=methodGetX.invoke(pt1);*/
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;
}
十四、 使用BeanUtils工具包操作JavaBean
Beanutils工具包
在工程下新建一个lib目录,将commons-beanutils.jar复制到目录下,然后增加到Build Path上。
增加后会变成:
package cn.cast.day1;
import java.util.Date;
public class ReflectPoint {
private int x;
private int y;
private Date birthday=new Date();
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public ReflectPoint(int x,int y){
this.x=x;
this.y=y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
public static void main(String[] args)throws Exception{
ReflectPoint pt1=new ReflectPoint(1,3);
BeanUtils.getProperty(pt1, "x");
System.out.println(pt1.getX());
}
出现了异常:
再引入logging 日志包。
public class IntroSpectorTest {
public static void main(String[] args)throws Exception{
ReflectPoint pt1=new ReflectPoint(1,3);
System.out.println(BeanUtils.getProperty(pt1, "x").getClass().getName());
//BeanUtils.getProperty(pt1, "x").getClass().getName();//获取
BeanUtils.setProperty(pt1, "x", 10);//设置
System.out.println(pt1.getX());
BeanUtils.setProperty(pt1, "birthday.time", "1990");
System.out.println(BeanUtils.getProperty(pt1,"birthday.time"));
PropertyUtils.setProperty(pt1, "x", 9);
System.out.println(PropertyUtils.getProperty(pt1, "x").getClass().getName());
}
结果:
java.lang.String
10
1990
java.lang.Integer
在前面例子基础上,用BeanUtils类先get原来设置好的属性,再将其set为一个新值。
get属性时,返回的结果为字符串,set属性时可以接收任意类型的对象,通常使用字符串。
用PropertyUtils类先get原来设置好的属性,再将其set为一个新值。
get属性时返回的结果为该属性本来的类型,set属性时只接收属性本来的类型。