反射技术
一、 反射应用场景&特点
在我们实际开发中会做一些软件,并且这个软件可以正常使用了,后期我们可能出现需要在原有软件上扩展一些功能。要扩展功能,我们都知道需要预留接口。后期需要对这个软件功能进行扩展的话,就需要写个类去实现接口,实现其中的方法。可是问题来了,软件已经正常使用,后期写的类,正常运行的软件无法预知,也无法使用新增加的功能,这时需要修改软件的源码,并且在源码中创建后期所写类的对象,并调用其中的方法。但这种做法不推荐,因为要改动源码,工程量大,并且不利于后期维护。
于是就想到能不能不要改变源码,而是基于接口,后期实现这个接口的程序,都把类名写到指定的配置文件中。这样原有的程序就可以去读取配置文件中的信息,并且根据读取到的信息动态创建类的对象,调用其中的功能呢?
要实现上述的这种功能,就需要使用Java中的反射技术来完成。
通过反射技术,获得配置文件中的类名,并且创建类的对象,调用其中的功能。反射技术的出现提高了程序的扩展性。
2、字节码文件对应的类
我们编写任意个Java源文件,在编译后都会生成一个class文件。Java语言是面向对象的语言,Java也对这些class文件进行了对象的描述。把所有的Java源文件编译后生成的class文件,使用Class这个类来描述。
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
原来在Java中所有的类、接口、数组、基本类型等都是Class类的实例。也就是我们在写一个类的时候,在类中写的成员变量、成员方法、静态方法、构造方法等都被封装成了对象。
二、 获取字节码
1. 获取字节码文件对象——getClass方法
在Object类中描述了一个方法为getClass,任何对象都具备这个方法,这个方法的功能就是获取当前对象所属class文件对象的。
/*
* 演示获取Class文件对象
*/
publicclass GetClassDemo {
public static void main(String[] args) {
Person p = newPerson("zhangsan",23);
Class clazz = p.getClass();
System.out.println(clazz);
}
}
2. 获取字节码文件对象——class属性
获取class文件对象第二种方式,任何对象,任意类型,包括基本类型,都拥有静态属性class,同样使用class属性也可以获得class文件对象。
public static void getClass_2() {
//获取基本类型对应的class文件对象
Class clazz = int.class;
System.out.println(clazz);
//获取自定义类对应的class文件对象
Class clazz2 = Person.class;
System.out.println(clazz2);
通过任意类型的静态成员属性class,可以获取到当前类型的class文件对象。这种好处是不用创建对象,直接获取,但是这种方式也要知道具体的类型才能获取。
3. 获取字节码文件对象——forname方法
Class既然是描述class文件对象的类,那么在其中就必定有获取class文件对象的功能。这个功能就是forName。
forName方法在使用的时候需要制定一个字符串对象,这个字符串对象就是class文件的全名称(报名+类名)。
public static void getClass_3() throwsClassNotFoundException {
String className ="cn.itcast.sh.domain.Person";
Class clazz =Class.forName(className);
System.out.println(clazz);
}
这种方式的好处:不需要具体的对象,不需要具体类型,只要给定要获取class文件的名称即可。通常给定的class文件名都是通过配置读取而获取到的。
4. 动态创建字节码对象所表示的类的对象
在未学习反射技术之前,要使用对象,必须使用new关键字来创建对象,然后在通过对象来调用方法。既然反射技术可以拿到class文件对象,那么就可以创建出class文件所描述的这类事物对象。如何创建这个对象呢?
查阅Class类的描述,在其中有newInstance方法,可以动态创建Class所表的类的实例对象。
/*
* 要使用Class中的newInstance方法动态创建类的对象,需要先加载这个类的class文件
*/
public static void getInstance() throwsClassNotFoundException, InstantiationException, IllegalAccessException {
/*
* 1、获取需要创建对象的class文件对象,
* 其实在使用forName方法的时候,会加载指定的class文件
*/
Class clazz =Class.forName("cn.itcast.sh.domain.Person");
/*
* 调用Class类中的newInstance方法获取Person对象。
* 在调用newInstance方法的时候会调用Person的无参数的构造方法,
* 如果Person.class文件没有无参数的构造方法,在调用newInstance方法时会发生InstantiationException异常
* 如果Person.class文件有无参数的构造方法,但是权限不够大,那么会发生IllegalAccessException异常
*/
Object obj = clazz.newInstance();
System.out.println(obj);
/* Person p = new Person();
* 1,加载Person类,并将Person类封装成字节码文件对象。
* 2,通过new创建Person对象。
* 3,调用构造函数对对象初始化。
*/
三、 反射类中的成员
class文件加载进内存后,被封装成Class对象,即就是在Class对象中存放这个class文件的所有信息。通过Class对象,就可以获取class文件中的相关信息,比如class文件中的构造函数,成员变量,成员方法等。
1. 反射构造函数
查阅Class的API描述信息,在其方法的描述中有getConstructor方法,可以获取到对应的构造方法对象,在使用时如果要获取拥有参数的构造方法对象,需要指定构造方法的形式参数类型。
publicclass GetConstructor {
public static void main(String[] args)throws Exception {
/*
* 如果要通过指定的构造函数初始化对象怎么办呢?
* 思路:
* 1,获取字节码文件对象。
* 2,再获取给定的构造函数。
* 3,通过构造函数初始化对象。
*/
getConstructorDemo();
}
public static void getConstructorDemo()throws Exception {
String className ="cn.itcast.sh.domain.Person";
Class clazz =Class.forName(className);
//获取指定的构造器。获取Person类中两个参数string,int的构造函数。
Constructor cons =clazz.getConstructor(String.class,int.class);
//有了构造器对象后,通过构造器对象来初始化给类对象。
Object obj = cons.newInstance("wangwu",23);
//Person p = newPerson("lisi",21);
System.out.println(obj);
}
}
2. 反射字段&暴力访问
Class类中的getField方法可以获取到class文件对象中公共的成员变量。
publicclass GetField {
public static void main(String[] args)throws Exception {
getFieldDemo();
}
public static void getFieldDemo() throwsException {
String className ="cn.itcast.sh.domain.Person";
Classclazz = Class.forName(className);
String fieldName = "age";
//获取age字段对象。
// Field field =clazz.getField(fieldName);//获取是公共的字段。
Field field =clazz.getDeclaredField(fieldName);//可以获取私有的成员字段
// getXXX:获取都是类中公共的成员。
// getDeclaredXXX:获取本类中已有的成员。
// System.out.println(field);
//对其进行值的设置,必须先有对象。
Object obj = clazz.newInstance();
//通过查找父类AccessiableObject的方法。setAccessiable(true);
field.setAccessible(true);//取消权限检查,暴力访问。一般不访问私有。
field.set(obj,30);//IllegalAccessException:age字段是私有的。
System.out.println(field.get(obj));
}
}
总结:当需要获取class文件中的私有成员,需要使用getDeclaredXxxx,同时也需要使用setAccessible(true)设置取消访问权限,否则无法访问这些私有的成员。
3. 反射方法
同样在Class类中的方法也可以获得class文件对象中的成员方法,已经私有成员方法和静态成员方法,并且通过invoke方法调用方法,让其运行
publicclass GetMethod {
public static void main(String[] args)throws Exception {
getMethodDemo2();
}
//演示获取Person类中的静态成员方法
public static void getMethodDemo2() throwsException {
String className ="cn.itcast.domain.Person";
Class clazz =Class.forName(className);
String methodName ="staticShow";
Method method =clazz.getMethod(methodName, null);
method.invoke(null, null);
}
//获取Person类中的非静态成员方法
public static void getMethodDemo() throwsException {
String className ="cn.itcast.domain.Person";
Class clazz =Class.forName(className);
String methodName ="show";
Method method =clazz.getMethod(methodName, String.class, int.class);
Object obj = clazz.newInstance();
method.invoke(obj,"wangcai", 20);
}
}