您的位置:首页 > 编程语言 > Java开发

java反射

2013-11-07 11:10 501 查看
反射:就是程序自己能够检查自身信息,就像程序能够通过镜子反光来查看自己本身一样。反射使得java语言具有了“动态性”,即程序首先会检查某个类中的方法、属性等信息,然后再动态的调用或动态创建该类或该类对象。

1.反射的基石-Class类

从JDK1.2开始就出现了Class类,该类用来描述java中的一切类事物,该类描述了类名字、类的访问属性、类所属的报名、字段名称的列表、方法名称的列表等。例如Class类的getName()方法可以获取所描述类的类名。

Class实例代表内存中的一份字节码,所谓字节码就是当java虚拟机加载某个类的对象时,首先需要把硬盘上该类的二进制源码翻译成class文件的二进制代码(字节码),然后把class文件的字节码加载到内存中,之后再创建该类的对象。

获取Class类的对象,可以有3种方式:

(1)最常用的方法为调用相应类对象的getClass()方法。如:

Data data;
Class dataclass = data.getClass ();
(2)通过Class类中的静态方法forName(),来获取与字符串对应的Class对象,例如

Class dataclass = Class.forName ("Data");
(3)通过类名.class来实现。例如:

Class dataclass = Data.class
下面通过一个类来演示类Class的用法:

package com.ccniit.lg.Class;
import java.lang.annotation.Annotation;
public class TestClass {
public static void main (String[] args) throws ClassNotFoundException {
String s1 = "1234";
//获取s1和String类的字节码
Class c1 = s1.getClass();
Class c2 = String.class;
Class c3 = Class.forName("java.lang.String");
//比较字节码是否相同
System.out.println("-----------------------------");
System.out.println("c1和c2是否是同一个对象:"+ (c1 == c2));
System.out.println("c1和c3是否是同一个对象:"+ (c1 == c3));
System.out.println("------------------------------");
//检测是否为基本类型
System.out.println("String是否是基本类型:"+String.class.isPrimitive());
System.out.println("int是否是基本类型:"+int.class.isPrimitive());
//检测int和Integer的字节码是否指向同一字节码
System.out.println("int和Integer的字节码是否是一个同一个对象:"+(int.class == Integer.class));
System.out.println("int和Integer.TYPE的字节码是否是一个同一个对象:"+(int.class == Integer.TYPE));
System.out.println("------------------------------");
//数组方面的字节码
System.out.println("int[]是否是基本类型:"+int[].class.isPrimitive());
System.out.println("int[]是否是数组类型:"+int[].class.isArray());
}
}
运行结果为:

-----------------------------
c1和c2是否是同一个对象:true
c1和c3是否是同一个对象:true
------------------------------
String是否是基本类型:false
int是否是基本类型:true
int和Integer的字节码是否是一个同一个对象:false
int和Integer.TYPE的字节码是否是一个同一个对象:true
------------------------------
int[]是否是基本类型:false
int[]是否是数组类型:true
2.反射的基本应用:

反射就是把Java类中的各种成分映射成相应的Java类。通过反射,在具体编写程序时,不仅可以动态的生成某个类中所需要的成员,而且还能动态调用相应的成员。不仅一个Java类可以用Class类的对象表示,而且Java类的各种成员,如成员变量、方法、构造方法、包等,也可以用相应的java类表示。

反射一般会涉及到以下类:Class(表示一个类的类)、Field(表示属性的类)、Method(表示方法的类)和Constructor(表示类的构造函数的类)。Class类中存在一系列的方法,来获取相关类中的变量、方法、构造方法、包等信息。
(1)构造方法的反射:

package com.ccniit.lg.ConstractorRef;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ConstrctorRef {
public static void main (String[] args) {
//创造字符串的常用方法
String s1 = new String (new StringBuffer("构造函数反射"));
System.out.println(s1);
Constructor constructor;
Constructor constructor2;
try {
//获取String类的构造函数对象
constructor = s1.getClass().getConstructor(StringBuffer.class);
System.out.println(constructor);
//通过Constructor类对象的方法创建字符串
String s2 = (String)constructor.newInstance(new StringBuffer("用反射得到的构造函数构造字符串"));
System.out.println(s2);
constructor2 = s1.getClass().getConstructor(String.class);
//String s3 = (String) constructor.newInstance("字符串");不能用这个来构造字符串,因为constructor的参数为StringBuffer,而不是String
//输入字符串的一些信息。
System.out.println("------------------------");
System.out.println("s1对象的第五个元素为:"+s1.charAt(4));
System.out.println("s2对象的第五个元素为:"+s2.charAt(4));
System.out.println("------------------------");
String s4 = (String) constructor2.newInstance("测试数据");
System.out.println(constructor2);
System.out.println(s4);

//得到s1的类的所有构造函数。
System.out.println("------------------------------------------");
Constructor[] constructors = s1.getClass().getDeclaredConstructors();
for (int i = 0;i < constructors.length;i++) {
System.out.println(constructors[i]);
}

//得到s1的类的所有公共构造函数
System.out.println("-------------------------------------------");
Constructor[] constructors2 = s1.getClass().getConstructors();
for (int i = 0;i < constructors2.length;i++) {
System.out.println(constructors2[i]);
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
运行结果为:

构造函数反射
public java.lang.String(java.lang.StringBuffer)
用反射得到的构造函数构造字符串
------------------------
s1对象的第五个元素为:反
s2对象的第五个元素为:到
------------------------
public java.lang.String(java.lang.String)
测试数据
------------------------------------------
public java.lang.String()
public java.lang.String(java.lang.String)
public java.lang.String(char[])
public java.lang.String(char[],int,int)
public java.lang.String(int[],int,int)
public java.lang.String(byte[],int,int,int)
public java.lang.String(byte[],int)
public java.lang.String(java.lang.StringBuilder)
public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],int,int,java.nio.charset.Charset)
public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],java.nio.charset.Charset)
public java.lang.String(byte[],int,int)
public java.lang.String(byte[])
public java.lang.String(java.lang.StringBuffer)
java.lang.String(int,int,char[])
-------------------------------------------
public java.lang.String()
public java.lang.String(java.lang.String)
public java.lang.String(char[])
public java.lang.String(char[],int,int)
public java.lang.String(int[],int,int)
public java.lang.String(byte[],int,int,int)
public java.lang.String(byte[],int)
public java.lang.String(java.lang.StringBuilder)
public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],int,int,java.nio.charset.Charset)
public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],java.nio.charset.Charset)
public java.lang.String(byte[],int,int)
public java.lang.String(byte[])
public java.lang.String(java.lang.StringBuffer)
代码解析:

a.在普通创建字符串的方式中,只要通过new String()方法就可以实现。

b.在通过反射方式实现创建字符串的方式中,首先需要获取String类的构造函数。在Class类中存在着getConstructor()方法,所以可以先通过String.class获取String类的字节码,然后再通过String.class.getConstructor()方法获取String类的构造函数。

c.获取到String类的构造函数后,在Constructor类中存在一个newInstance()方法,用来利用构造函数创建一个实例对象。该方法的参数类型必须与获取的构造函数的参数类型相同。

(2)成员字段的反射:

package com.ccniit.lg.FeildRef;
public class Point {
private int x;
public int y;
public String s1 = "aaaaaaaaaaaaaaaa";
public String s2 = "bbbbbbbbbbbbbbbb";

public Point (int x,int y) {
super();
this.x = x;
this.y = y;
}

public Point (int x,int y,String s1,String s2) {
super();
this.x = x;
this.y = y;
this.s1 = s1;
this.s2 = s2;
}

public String toString () {
return "s1的值为:"+s1+",s2的值为:"+s2;
}
}

package com.ccniit.lg.FeildRef;
import java.lang.reflect.Field;
import javax.swing.text.ChangedCharSetException;
public class FiledRef {
public static void main (String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Point point = new Point(3, 4);//定义一个坐标
//获取字段y的值
Field fieldY = point.getClass().getField("y");//获取类中的类字段
System.out.println(fieldY);
System.out.println("输出public属性字段:"+fieldY.get(point));//获得对象中的值,获取字段y的值
Field fieldX = point.getClass().getDeclaredField("x");//获取类中的类字段
fieldX.setAccessible(true);//将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。
System.out.println("输出private属性字段:"+fieldX.get(point));
Chang(point);
System.out.println(point);

Field[] fields = point.getClass().getDeclaredFields();//获得所有的成员字段。
for (int i = 0;i < fields.length;i++) {
System.out.println(fields[i]);
}
}
//通过反射改变字段中的字母方法
private static void Chang(Object obj) throws IllegalArgumentException, IllegalAccessException {
Field[] fields = obj.getClass().getFields();//获得所有的公共成员字段
for (Field field : fields) {//遍历字段数组
if (String.class == field.getType()) {//当类型为String类型时
String oldValue = (String) field.get(obj);//获取成员字段的值
String newValue = oldValue.replace('a', 'b');//实现替换
field.set(obj, newValue);
}
}
}
}
运行结果为:

public int com.ccniit.lg.FeildRef.Point.y
输出public属性字段:4
输出private属性字段:3
s1的值为:bbbbbbbbbbbbbbbb,s2的值为:bbbbbbbbbbbbbbbb
private int com.ccniit.lg.FeildRef.Point.x
public int com.ccniit.lg.FeildRef.Point.y
public java.lang.String com.ccniit.lg.FeildRef.Point.s1
public java.lang.String com.ccniit.lg.FeildRef.Point.s2
代码解析:

a.在上述代码中,可以通过"point.getClass().getField("y")",获取point对象字节码中字段y的Field对象fieldY。fieldY对象是Point类上面的成员字段,而不是point对象上的成员字段,这是因为类只有一个,而类的实例对象却有多个,如果对应到对象的成员字段上,则没办法确定关联到哪个对象上。

b.由于fieldY对象是Point类上面的成员字段,所以如果要得到point对象字段y的值,必须通过以point对象为参数的get()方法来实现。

c.通过反射方法getField()得到的Field对象,只能是public修饰的字段。如果想获得其他属性字段的Field对象,则必须通过getDeclaredField()方法。获取到Field对象后,如果想获取相应对象上该字段的值,还必须通过setAccessible()进行设置。
d.在上述代码中还存在一个方法chang(),该方法主要用来实现把一个对象中所有的String类型的成员字段,所对应的字符串中的a改为b.

(3)成员方法的反射:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MethodRef {
public static void main (String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
String s1 = "abcdef";//定义一个字符串
//获取String类中参数为int的charAt()方法
Method methodCharAt = String.class.getMethod("charAt", int.class);
System.out.println(methodCharAt);
//对象s1调用charAt()方法
System.out.println("对象s1中第二个字母为:"+methodCharAt.invoke(s1, 1));//Method的invoke()方法返回使用参数 args 在 obj 上指派该对象所表示方法的结果
System.out.println("对象s1中第二个字母为:"+methodCharAt.invoke(s1, new Object[] {1}));
}
}
运行结果为:

public char java.lang.String.charAt(int)
对象s1中第二个字母为:b
对象s1中第二个字母为:b
代码解析:
a.在上述代码中,首先创建了一个字符串对象s1,接着通过s1字节码对象的getMethod()方法获取String类中带有int类型参数的charAt()方法。
b.通过反射方式获得Method对象后,如果想要调用此方法,可以通过invoke()方法来实现。invoke()方法的定义如下:

public Object invoke(Object obj,Object... args)
第一个参数为实体对象,第二个参数为方法调用所需要的参数。

如果传递给invoke()方法的第一个参数为null对象,说明该Method对象对应的是一个静态方法。如下所示,通过类的反射来调用类的main()方法:

package com.ccniit.lg.StaticMainRef;
public class StaticMain {
public static void main (String[] args) {
System.out.println("------------------------");
for (String arg : args) {
System.out.println(arg);
}
}
}


package com.ccniit.lg.StaticMainRef;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class StaticMainRef {
public static void main (String[] args) throws SecurityException, NoSuchMethodException, ClassNotFoundException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
//调用类的静态方式
StaticMain.main(new String []{"111","222","333","444"});
//通过反射调用类的静态方法
startClass ("com.ccniit.lg.StaticMainRef.StaticMain");
}

//通过反射调用类的静态方法的方法
private static void startClass(String className) throws SecurityException, NoSuchMethodException, ClassNotFoundException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
//获取相应类的main()方法
Method mainMethod = Class.forName(className).getMethod("main", String[].class);
//执行main方法
mainMethod.invoke(null, new Object[] {new String[] {"111","222","333","444"}});
//执行main方法
mainMethod.invoke(null,(Object) new String[] {"111","222","333","444"});
}
}


运行结果如下:

------------------------
111
222
333
444
------------------------
111
222
333
444
------------------------
111
222
333
444
3.反射的高级应用:

数组等集合方法也能反射。

(1)数组的反射:Array类为数组的字节码,Arrays类主要用来实现数组的各种操作。

//
package com.ccniit.lg.ArrayRef;
import java.lang.reflect.Array;
import java.util.Arrays;
public class ArrayRef {
public static void main (String[] args) {
//创建了4种类型的数组
int[] a1 = new int[] {1,2,3};//一维int数组
int[] a2 = new int[4];//一维int数组
int[][] a3 = new int[2][3];//二维int数组
String[] a4 = new String[] {"1","2","3"};//二维String数组
System.out.println("------------------------------");
//判断数组a1和数组a2字节码是否相同
System.out.println("a1与a2的字节码是否相同:"+(a1.getClass() == a2.getClass()));
//Array类的一些方法
System.out.println("------------------------------");
System.out.println("a3数组的名字:"+a3.getClass().getName());
System.out.println("a1数组超类的名字:"+a1.getClass().getSuperclass().getName());
//数组与Object[]的对应关系
Object obj1 = a1;
//Object[] obj2 = a1;
Object obj3 = a4;
Object[] obj4 = a4;
Object obj5 = a3;
Object[] obj6 = a3;
System.out.println("---------------------------------");
System.out.println("无工具类Arrays的输出:"+a1);
System.out.println("无工具类Arrays的输出:"+a4);
System.out.println("---------------------------------");
//调用工具类Arrays的asList()方法
System.out.println("有工具类Arrays的输出:"+Arrays.asList(a1));
System.out.println("有工具类Arrays的输出:"+Arrays.asList(a4));
System.out.println("---------------------------------");
pritObject(a1);
pritObject(1);
}
//打印对象中的成员方法
private static void pritObject(Object obj) {
Class class1 = obj.getClass();//获取字节码
if (class1.isArray()) {//判断是否为数组
System.out.println("调用自定义方法的数组的输出");
int len = Array.getLength(obj);//获取数组长度
for (int i = 0;i < len;i++) {//获取数组中的各个成员
System.out.println(Array.get(obj, i));
}
System.out.println("--------------------------------");
}else {//输出相应信息
System.out.println("调用自定义的方法的普通对象的输出");
System.out.println(obj);
System.out.println("--------------------------------");
}
}
}


package com.ccniit.lg.ArrayRef;
import java.lang.reflect.Array;
import java.util.Arrays;
public class ArrayRef {
public static void main (String[] args) {
//创建了4种类型的数组
int[] a1 = new int[] {1,2,3};//一维int数组
int[] a2 = new int[4];//一维int数组
int[][] a3 = new int[2][3];//二维int数组
String[] a4 = new String[] {"1","2","3"};//二维String数组
System.out.println("------------------------------");
//判断数组a1和数组a2字节码是否相同
System.out.println("a1与a2的字节码是否相同:"+(a1.getClass() == a2.getClass()));
//Array类的一些方法
System.out.println("------------------------------");
System.out.println("a3数组的名字:"+a3.getClass().getName());
System.out.println("a1数组超类的名字:"+a1.getClass().getSuperclass().getName());
//数组与Object[]的对应关系
Object obj1 = a1;
//Object[] obj2 = a1;
Object obj3 = a4;
Object[] obj4 = a4;
Object obj5 = a3;
Object[] obj6 = a3;
System.out.println("---------------------------------");
System.out.println("无工具类Arrays的输出:"+a1);
System.out.println("无工具类Arrays的输出:"+a4);
System.out.println("---------------------------------");
//调用工具类Arrays的asList()方法
System.out.println("有工具类Arrays的输出:"+Arrays.asList(a1));
System.out.println("有工具类Arrays的输出:"+Arrays.asList(a4));
System.out.println("---------------------------------");
pritObject(a1);
pritObject(1);
}
//打印对象中的成员方法
private static void pritObject(Object obj) {
Class class1 = obj.getClass();//获取字节码
if (class1.isArray()) {//判断是否为数组
System.out.println("调用自定义方法的数组的输出");
int len = Array.getLength(obj);//获取数组长度
for (int i = 0;i < len;i++) {//获取数组中的各个成员
System.out.println(Array.get(obj, i));
}
System.out.println("--------------------------------");
}else {//输出相应信息
System.out.println("调用自定义的方法的普通对象的输出");
System.out.println(obj);
System.out.println("--------------------------------");
}
}
}
运行结果为:

------------------------------
a1与a2的字节码是否相同:true
------------------------------
a3数组的名字:[[I
a1数组超类的名字:java.lang.Object
---------------------------------
无工具类Arrays的输出:[I@de6ced
无工具类Arrays的输出:[Ljava.lang.String;@c17164
---------------------------------
有工具类Arrays的输出:[[I@de6ced]
有工具类Arrays的输出:[1, 2, 3]
---------------------------------
调用自定义方法的数组的输出
1
2
3
--------------------------------
调用自定义的方法的普通对象的输出
1
--------------------------------
代码解析:

a.通过“a1和a2数组的字节码相同”的运行结果可以看出:具有相同维数和元素类型的字节码相同,即具有相同的Class实例对象。

b.通过调用Class实例对象的getName()方法可以返回字节码名字,例如"[[I"表示是二维数组,类型是int。通过调用Class实例对象的getSuperclass()方法可以返回父类,任何数组字节码的父类都是Object类对应的字节码,例如a1数组父类的名字java.lang.Object。

c.当直接输出数组时,得到的输出结果不理想。例如数组a1的输出结果为"[I@de6ced",其中"[I"表示为int类型数组,"de6ced"表示该对象的hascode值;数组a4的输出结果为"[Ljava.lang.String;@c17164",其中"[Ljava.lang.String"表示为String类型数组,"c17164"表示该对象的hascode值。

d.通过工具类Arrays的asList()方法可以输出数组中参数的值,例如数组a4的输出结果为"[1, 2, 3]",但是可以看见a1的输出结果为"[[I@de6ced]",而不是"[1, 2, 3]"。这是因为数组a1为int类型的一维数组,只能转换为Object对象,为不能转换为Object[]对象。即基本类型的一维数组可以被当做Object类型使用,而不能当做Object[]类型使用(Object[] obj2 = a1是错误的)。非基本类型的一维数组,既可以被当做Object类型使用,也可以被当做Object[]类型使用。

e.为了避免工具类Arrays的asList()方法的弊端,所以编写了一个名为printObject的方法,该方法可以打印出对象的成员,不管该对象是一个数组还是一个对象。

(2)集合的反射:
package com.ccniit.lg.CollectRef;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;
public class CollectRef {
public static void main (String[] args) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
//读取属性文件
InputStream is = new FileInputStream( "Config.properties");
Properties props = new Properties();//创建Properties类型对象
props.load(is);
is.close();
String className = props.getProperty("className");//获取相应的值
//创建相应的集合对象
Collection collections = (Collection) Class.forName(className).newInstance();
//为集合collections添加数据
collections.add("1");
collections.add("2");
collections.add("3");
collections.add("4");
System.out.println("collections集合中的成员:"+collections);
}
}
运行结果为:

collections集合中的成员:[1, 2, 3, 4]
注:上述代码中要想运行成功,还必须在src目录下面创建一个名为Config.properties的文件,该文件代码如下:

className=java.util.ArrayList


本篇内容为阅读《java典型模块与项目实战大全》后所做笔记。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 教程 反射