反射:就是程序自己能够检查自身信息,就像程序能够通过镜子反光来查看自己本身一样。反射使得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典型模块与项目实战大全》后所做笔记。