第10章 反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的属性和方法对于任意一个对象,都能够调用到它的任意一个方法和属性。这种状态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
10.1 反射的基石—Class类
1、简介
Java类用于描述一类事物,该类事物有什么属性,没什么属性,至于这个属性的值是什么,则使用这个类的实例对象来确定的。Java程序中的各个java类属于同一类型事物,描述这类事物的Java类名就是Class。
2、获取字节码文件的方式
要想对字节码文件进行解剖,必须要有字节码文件对象如何获取字节码文件对象?以下为获取对象有三种方式的示例
publicclass GetClass {
publicstaticvoid main(String[] args) throws Exception {
String s = "fdjak";
//方式一:Object类中的getClass方法的,想要用这种方式,必须明确具体的类并创建对象
Class clazz1 = s.getClass();
//方式二:任何数据类型都具备一个静态的属性.class来获取其对应的Class对象 相对简单,但
//是还是要用到类中的静态成员。还是不够扩展
Class clazz2 = String.class;
/*
方式三:只要通过给定的类的字符串名称就可获取该类,更为扩展,可以用Class类中的方法
完成。该方法就是forName,返回字节码文件,如果内存中有该字节码文件就直接返回,如果
没有,先加载然后在返回这种方式只要有名称即可,更为方便,扩展性强。
*/
Class clazz3 = Class.forName("java.lang.String");
System.out.println(clazz1==clazz2);
System.out.println(clazz1==clazz3);
//String是一个类,不是基本类型
System.out.println(clazz1.isPrimitive());
System.out.println(int.class.isPrimitive());
System.out.println(int.class ==Integer.class);
//九种基本类型可以用(该类型的包装类.TYPE)来表示
System.out.println(int.class ==Integer.TYPE);
}
}
3、Class
实例对象
⑴、预定义的 Class
实例对象
九个预定义的Class
实例对象,表示八个基本类型和 void。这些类对象由 Java虚拟机创建,与其表示的基本类型同名,即
boolean
、byte
、char
、short
、int
、long
、float
和
double
。
⑵、数组的Class实例对象
总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如int[] ,void
10.2 反射
反射就是把java类中的各种成分映射成相应的java类。一个类中的每一个成员都可以用相应的反射API类的一个实例对象来表示。
1、Constructor类
Constructor类代表某个类中的一个构造方法。当要获取指定名称对应类中的所体现的对象时,而该对象初始化下不使用空参构造方法该怎么办?既然是通过指定的构造方法进行对象的初始化,应该先获取到构造方法,通过字节码文件对象即可完成,该方法是:getConstructor(paramterTypes);
privatestaticvoidcreateNewObject_2() throws Exception {
//反射机制.Person p = new 反射机制.Person("xiaoqiang",10);
Stringname = "反射机制.Person";
Class<?>clazz = Class.forName(name);
//获取到了指定的构造方法对象
Constructor<?>constructor = clazz.getConstructor(String.class,int.class);
//通过该构造器对象的newInstance方法进行对象的初始化
Objectobj = constructor.newInstance("xiaoming",45);
}
publicstaticvoidcreateNewObject_1() throws Exception {
//早期:new的时候,先根据被new的类的名称寻找该类的字节码文件,并加载进内存并创建该字
//节码文件对象,并接着创建该字节文件的对应的Person对象。反射机制.Person p = new 反射
//制.Person();
//现在:
Stringname = "反射机制.Person";
//找寻该名称类文件,加载进内存,并产生Class对象
Classclazz = Class.forName(name);
//如何产生该类对象呢?用newInstance()可以创建一个实例,并利用空参构造方法初始化该对象
//当Person类中没有空参构造方法,就会报InstantiationException,如果空参构造方法被私有
//化,就会发生IllegalAccessException
Objectobj = clazz.newInstance();
}
}
2、Field类
Field类代表某个类中的一个成员变量
publicstaticvoidmain(String[] args) throws Exception {
Class clazz = Class.forName("反射机制.Person");
//getField方法访问私有的方法会发生NoSuchFieldException,因为他只能获取公有的字段,
Field field = null;//clazz.getField("age");
//getDeclaredField方法只获取本类的,但包含私有
field = clazz.getDeclaredField("age");
//对私有字段访问取消权限检查,暴力访问
field.setAccessible(true);
Object obj = clazz.newInstance();
field.set(obj, 54);
Object o = field.get(obj);
System.out.println(o);
}
3、Method类
//获取所有的方法
privatestaticvoidgetMethod1() throws ClassNotFoundException {
Classclazz = Class.forName("反射机制.Person");
//获取的都是公有的方法
Method[] method = clazz.getMethods();
//获取的都是本类中的所有方法,包含私有
method = clazz.getDeclaredMethods();
for(Method method2 : method) {
System.out.println(method2);
}}
//获取单个无参方法方法
privatestaticvoid getMethod2() throws Exception {
Class<?>clazz = Class.forName("反射机制.Person");
//获取空参数一般方法
Methodmethod = clazz.getMethod("show",null);
//运行方法需要对象
Objectobj = clazz.newInstance();
Constructor<?>con = clazz.getConstructor(String.class,int.class);
Objectobj1 = con.newInstance("ciao",365);
method.invoke(obj1,null);
}
//获取单个有参方法方法的方式
privatestaticvoidgetMethod3() throws Exception {
Classclazz = Class.forName("反射机制.Person");
Methodmethod = clazz.getMethod("paramMethod",String.class,int.class);
Objectobj = clazz.newInstance();
method.invoke(obj,"xiaocqiang",65 );
}
注意:jdk1.4和jdk1.5的invoke方法的区别
在jdk1.4没有可变参数,因此当有多个参数时,要将这些参数封装成一个数组,然后传递给invoke方法,数组中的每个元素分别对应被调用方法中的一个参数。
publicvoid test() throws Exception {
Stringstr1 = "adfawe";
//jdk1.5的invoke方法的形式
System.out.println(methodCharAt.invoke(str1, 1));
//jdk1.4的invoke方法的传参形式
System.out.println(methodCharAt.invoke(str1, new Object[]{2}));
//如果传递给Method对象的invoke()方法的第一个参数为null,这意味着Method对象对应的是一个静态方法
System.out.println(methodCharAt.invoke(null, 1));
}
10.3 反射的应用
10.3.1 用反射方式执行某个类中的main方法
⑴、缘由:之所以要用反射方式执行某个类中的main方法,是因为在程序中的某个类在运行到某处需要去调用其他类的main方法时,如果此程序并不知道此main方法所属类的名称,而只是在程序中接受某一代表此main方法所属类的名称的参数,那么这时候就不能通过“类名.main(String[] args);"这样的方式来完成调用,而需要运用Java的反射机制了,需要编写相关的反射代码来完成对其他类的main方法的调用。
⑵、问题:启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法设置参数呢?
按jdk 1.5的语法,由于使用的是可变参数(Object类型),设置的数组参数会被作为一个参数进行传递,而按jdk 1.4的语法,此处应设置一个Object数组,数组中的每个元素对应所调用方法的一个参数。
当把一个字符串数组作为参数传递给invoke方式时,编译器会兼容jdk 1.4的语法,即按照1.4的语法进行处理,即把字符串数组打散成为若干个单独的参数,这样就会产生参数个数不匹配的异常。
解决办法:
mainMethod.invoke(null,newObject[](new String[]{“xxxx”}))打包成一个
mainMethod.invoke(null,(Object)(newString[]{“xxxx”}))采用上述强制向上转型后,可以是编译器按照正确的方法进行参数处理,即将整个字符串参数作为整体传递给目标main方法。
10.3.2、数组的反射
1、每一个具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
class ArrayReflect {
publicstaticvoid main(String[] args) {
int[] a1 = newint[2];
int[] a2 = newint[4];
System.out.println(a1.getClass() == a2.getClass());
}
}
2、代表数组的Class实例对象的个体Superclass()方法返回的父类为Object类对应的Class
class ArrayReflect {
publicstaticvoid main(String[] args) {
int[] a1 = newint[]{1,2,3};
System.out.println(a1.getClass().getName());
System.out.println(a1.getClass().getSuperclass().getName());
}
}
3、基本类型的一维数组可以被当做Object类型使用,但不能当做Object[]类型使用,非基本类型即可当做Object类型使用,又可以当做Object[]类型使用。
class ArrayReflect {
publicstaticvoid main(String[] args) {
int[] a1 = newint[]{1,2,3};
int[][] a3 = newint[2][3];
String[] a4 = new String[]{"a","v","c"};
Object aobj1 = a1;
Object aobj2 = a4;
// Object[]aobj3 = a1;报错:a1里面存的是int型数据,而不是Object
Object[] aobj4 = a3;
Object[] aobj5 = a4;
}
}
4、Arrays.asList()方法处理int[]和String[]时的差异
class ArrayReflect {
publicstaticvoid main(String[] args) {
int[] a1 = newint[]{1,2,3};
String[] a4 = new String[]{"a","r","c"};
System.out.println( Arrays.asList(a4));
System.out.println( Arrays.asList(a1));
}
}
打印结果:[a, r, c] [[I@55e55f]。之所以如此也和3的原理差不多
5、数组的反射
如何使用反射的方式来获取、设置数组中的元素,或者获取数组的长度?
privatestaticvoidprintObject(Object obj) {
Class clazz = obj.getClass();
if(clazz.isArray()) {
int len = Array.getLength(obj);
for(int i= 0; i<len; i++){
//get()方法返回指定数组对象中索引组件的值。
System.out.println(Array.get(obj, i));
}
}else {
System.out.println(obj);
}
}
怎么获得数组的类型?结论是:没有办法获得数组的类型。
反射的作用——实现框架
框架与框架要解决的核心问题