------- android培训、java培训、期待与您交流! ----------
Java反射机制
一、概述
反射就是把Java类中的各种成分映射成相应的java类。例如:一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个类。表示Java类的Class显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。
反射的基石-->Class 类
Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。
Class类代表Java类,它的各个实例对象又分别对应什么呢?
对应各个类在内存中的字节码。例如:Person类的字节码,ArrayList类的字节码,等等。一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?
如何得到各个字节码对应的实例对象(Class类型)?
类名.class。例如:System.class
对象.getClass()。例如:new Date().getClass();
Class.forName("类名")。例如:Class.forName("java.util.Date");
九个预定义Class实例对象
boolean、byte、char、short、int、long、float、 double 和 void
参看Class.isPrimitive()方法。
int.class == Integer.TYPE
数组类型的Class实例对象
Class.isArray();
总之,只要是在源程序中出现的类型,都有各自的Class实例对象。例如:int[],void
System.out.println(int.class.isPrimitive()); //true
System.out.println(int.class == Integer.class); //false
System.out.println(int.class == Integer.TYPE); //true
System.out.println(int[].class.isPrimitive()); //false
System.out.println(int[].class.isArray()); //true
Java类用于描述一类事物的共性。该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值。Java程序中的各个Java类,它们是否属于同一类事物,是不是可以用一个类来描述这类事物呢?这个类的名字就是Class,要注意与小写class关键字的区别哦。Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属于的的包名,字段名称的列表。方法名称的列表,等等。
二、构造方法的反射应用
Constructor 类
Constructor 类代表某个类中的一个构造方法。
得到某个类所有的构造方法:
例子:
Constructor[] constructors = Class.forName("java.lang.String").getConstructors();
得到某一个构造方法:
例子:
Constructor constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);//获得方法时要用到类型
创建实例对象:
通常方式: String str = new String(new StringBuffer("abc"));
反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc"));//调用获得的方法时要用到上面相同类型的实例对象。
Class.newInstance()方法:
例子: String obj = (String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。
//new String(new StringBuffer("abc"));
Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
String str2 = (String)constructor1.newInstance(new StringBuffer("abc"));
System.out.println(str2.charAt(2)); //c
一个类有多个构造方法,用什么方式可以区分清楚想得到其中的哪个方法呢?根据参数的个数和类型。
例如:Class.getMethod(name,Class... args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。
重点: 参数类型用什么方式表示?用Class实例对象。
例如:
int .class, (int[]).class
int[] ints = new int[0];
ints.getClass();
Constructor 对象代表一个构造方法,大家觉得Constructor对象上会有什么方法呢?得到名字,得到所属于的类,产生实例对象。
三、成员变量的反射
Field 类
Field 类代表某个类中的一个成员变量。
演示用eclipse自动生成Java类的构造方法。
问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?
类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象呢?
所以字段fieldX 代表的是X的定义,而不是具体的X变量。
示例代码:
ReflectPoint类的定义:
public class ReflectPoint {
private int x;
public int y;
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
ReflectPoint pt1 = new ReflectPoint(3, 5);
Field fieldY = pt1.getClass().getField("y");
//fieldY的值是多少?是5,错!fieldY不是对象身上的变量,而是类上的,要用它去取某个对象上对应的值。
System.out.println(fieldY.get(pt1));
Field fieldX = pt1.getClass().getDeclaredField("x"); //x是私有的,要用getDeclaredField()方法
fieldX.setAccessible(true); //
System.out.println(fieldX.get(pt1));
}
三、成员方法的反射
Method 类
Method 类代表某个类中的一个成员方法。
得到类中的某一个方法:
例子:
Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
调用方法:
通常方式: System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1));
如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?
说明该Method对象对应的是一个静态方法。
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)); //b
System.out.println(methodCharAt.invoke(str1, new Object[]{2})); //c
大家应通过思考和推理的方式来学习反射中的API。例如:Class.getMethod方法用于得到一个方法,该方法要接受什么参数呢?
显然要一个方法名,而一个同名的方法有多个重载形式,用什么方式可以区分清楚想得到重载方法系列中的哪个方法呢?
根据参数的个数和类型,例如:Class.getMethod(name, Class... args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。
四、对接收数组参数的成员方法进行反射
用反射方式执行某个类中的main方法
目标:
写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
问题:
启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5 的语法,整个数组是一个参数,而按jdk1.4 的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,java会到底按照哪种语法进行处理呢?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"}); 编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。
示例代码:
public class ReflectTest {
public static void main(String[] args) throws Exception{
String startingClassName = args[0];
Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);
mainMethod.invoke(null, (Object)new String[]{"111","222","333"});
}
}
class TestArguments{
public static void main(String[] args)
{
for(String arg : args)
{
System.out.println(arg);
}
}
}
五、数组的反射应用
数组的反射
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperclass()方法返回的父类为Object类对应的Class。
基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;
非基本类型的一维数组,既可以当作Object类型使用,又可以当作Object[]类型使用。
Arrays.asList()方法处理int[]和String[]时的差异。
怎么得到数组中的元素类型?得不到
Array工具类用于完成对数组的反射操作。
示例代码:
public class ReflectTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
String[] a6 = new String[]{"a","b","c"};
printObject(a6);
printObject("xyz");
}
private static void printObject(Object obj) {
Class clazz = obj.getClass();
if(clazz.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);
}
}
}
六、反射的作用
反射的作用——>实现框架功能
框架与框架要解决的核心问题
我做房子卖给用户住,由用户自己安装门窗和空调。我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。
框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
框架要解决的核心问题
我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象了,而要用反射方式来做。
综合案例
先直接用new语句创建ArrayList和HashSet的实例对象,演示用eclipse自动生成ReflectPoint类的equals和hashCode方法,比较两个集合的运行结果差异。
然后改为采用配置文件加反射的方式创建ArrayList和HashSet实例对象,比较观察运行结果差异。
引入了eclipse对资源文件的管理方式的讲解。
ReflectPoint类的定义:
public class ReflectPoint {
private int x;
public int y;
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ReflectPoint other = (ReflectPoint) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}
在eclipse的javaenhance工程文件中创建一个配置文件:config.properties,内容为:className=java.util.ArrayList
主程序代码:
public class ReflectTest2 {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
//反射
/*
getRealPath();
一定要记住用完整的路径,但完整的路径不是硬编码,而是运算出来的。
*/
//InputStream ips = new FileInputStream("config.properties");
//类加载器
//InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn\\itcast\\day01\\config.properties");
//InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day01/config.properties");
//InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");
//把配置文件config.properties放在cn\itcast\day01\resources文件夹里。
//InputStream ips = ReflectTest2.class.getResourceAsStream("resources/config.properties"); //相对路径
InputStream ips = ReflectTest2.class.getResourceAsStream("cn/itcast/day01/resources/config.properties"); //绝对路径
Properties props = new Properties();
props.load(ips);
ips.close();
String className = props.getProperty("className");
Collection collections = (Collection)Class.forName(className).newInstance();
/**Java中的集合(Collection)有两类,一类是List,再有一类是Set。*/
//Collection collections = new ArrayList(); //元素是有序的,元素可以重复
//Collection collections = new HashSet(); //元素无序,但元素不可重复
ReflectPoint pt1 = new ReflectPoint(3,3);
ReflectPoint pt2 = new ReflectPoint(5,5);
ReflectPoint pt3 = new ReflectPoint(3,3);
collections.add(pt1);
collections.add(pt2);
collections.add(pt3);
collections.add(pt1);
//pt1.y = 7; //如果没有这一句,结果为:1。(有这一句内存泄露)
//collections.remove(pt1); //结果为:2
System.out.println(collections.size());
//ArrayList结果:4; HashSet结果:3,有了hashCode()和equals()方法之后,结果为:2.
}
}
七、JavaBean
JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法的名称叫什么好呢?JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,至于你把它存到哪个变量上,用管吗?如果方法名为getId,中文意思即为获取id,至于你从哪个变量上取,用管吗?去掉set前缀,剩余部分就是属性名。如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。
setId()的属性名——>id
isLast()的属性名——>last
setCPU()的属性名是什么?——>CPU
getUPS()的属性名是什么?——>UPS
总之,一个类被当作JavaBean调用时,JavaBean的属性是根据方法推断出来的,它根本看不到java类内部的成员变量。
一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean。
好处如下:
在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,则人都这么用和要求这么做,那你就没什么挑选的余地!
JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的X,怎么做,有一定难度吧?用内省这套API操作JavaBean比用普通类的方式更方便。
示例代码:
public class ReflectPoint {
private int x;
public int 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 ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
public class IntroSpectorTest {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
ReflectPoint pt1 = new ReflectPoint(3, 5);
String propertyName = "x";
//"x"-->"X"-->"getX"-->MethodGetX-->
//get
Object retVal = getProperty(pt1, propertyName);
System.out.println(retVal);
//set
Object value = 7;
setProperty(pt1, propertyName, value);
System.out.println(pt1.getX());
}
private static void setProperty(Object pt1, String propertyName,
Object value) throws IntrospectionException,
IllegalAccessException, InvocationTargetException {
PropertyDescriptor pd2 = new PropertyDescriptor(propertyName, pt1.getClass());
Method methodSetX = pd2.getWriteMethod();
methodSetX.invoke(pt1, value);
}
private static Object getProperty(Object pt1, String propertyName)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
PropertyDescriptor pd = new PropertyDescriptor(propertyName, pt1.getClass());
Method methodGetX = pd.getReadMethod();
Object retVal = methodGetX.invoke(pt1);
return retVal;
}
}
采用遍历BeanInfo的所有属性方式来查找和设置某个ReflectPoint对象的X属性。在程序中把一个类当作JavaBean来看,就是调用IntroSpector.getBeanInfo方法,得到的BeanInfo对象封装了把这个类当作JavaBean看的结果信息。
示例代码:
private static Object getProperty(Object pt1, String propertyName)
throws IntrospectionException, IllegalAccessException,
InvocationTargetException {
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工具包
演示用eclipse如何加入jar包,先只是引入Beanutils包,等程序运行出错后再引入logging包。
在前面内省例子的基础上,用Beanutils类先get原来设置好的属性,再将其set为一个新值。get属性时返回的结果为字符串,set属性时可以接受任意类型的对象,通常使用字符串。
用PropertyUtils类先get原来设置好的属性,再将其set为一个新值。get属性时返回的结果为该属性本来的类型,set属性时只接受该属性本来的类型。
//BeanUtils工具包
//System.out.println(BeanUtils.getProperty(pt1, "x")); //7
System.out.println(BeanUtils.getProperty(pt1, "x").getClass().getName()); //java.lang.String
BeanUtils.setProperty(pt1, "x", "9"); //9
System.out.println(pt1.getX());
BeanUtils.setProperty(pt1, "birthday.time", "111");
System.out.println(BeanUtils.getProperty(pt1, "birthday.time")); //111
PropertyUtils.setProperty(pt1, "x", 9);
System.out.println(PropertyUtils.getProperty(pt1, "x").getClass().getName()); //java.lang.Integer