反射概述
框架
半成品软件。可以在框架的基础上进行软件开发,简化编码。学习框架并不需要了解反射,但是要是想自己写一个框架,那么就要对反射机制有很深入的了解。
反射机制
Java的反射(Reflection)机制是Java的特性之一,反射机制是构建框架技术的基础所在。灵活掌握Java反射机制,对以后学习框架技术有很大的帮助。
Java的反射机制是指在运行状态中,动态获取信息以及调用对象方法的功能。
Java反射机制是在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象方法的功能称为反射。
Java反射有3个动态性质:运行时产生对象实例、运行期间调用方法、运行时更改属性。
反射原理
要想Java程序能够运行,Java类必须被java虚拟机加载。运行的程序都是在编译时就已经加载了所需要的类。Java反射机制在编译时并不确定是哪个类被加载了,而是在程序运行是才加载、探知、使用,这样的特点就是反射。这类似于光学中反射的概念。所以把Java的这种机制称为反射机制。在计算机科学领域,反射指一类应用,它们能够自描述和自控制。
Java代码在计算机中经历的三个阶段:
(1)Source源代码阶段:.Java被编译器编译成*.class字节码文件。
(2)Class类对象阶段:*.class字节码文件被类加载器加载进内存,并将其封装成Class对象(用于在内存中描述字节码文件),Class对象将原字节码文件中的成员变量抽取出来封装成数组Field[],将原字节码文件中的构造函数抽取出来封装成数组Construction[],在将成员方法封装成Method[]。当然Class类内不止这三个,还封装了很多,我们常用的就这三个。
(3)RunTime运行时阶段:创建对象的过程new。
自审
Java反射机制能够知道类的基本结构,这种对Java类结构的探知能力,称为Java类的“自审”。在使用IDEA时,Java代码的自动提示功能,就是利用了Java反射的原理,是对创建对象的探知和自审。
通过Java反射,可以实现以下功能:
-
在运行时判断任意一个对象所属的类。
-
在运行时构造任意一个类的对象。
-
在运行时判断任意一个类所具有的方法和属性。
-
在运行时调用任意一个对象的方法。
反射的好处
1、可以在程序运行过程中,操作这些对象。
2、可以解耦,提高程序的可扩展性。
Java反射常用API
使用Java反射技术常用的类:
-
Class类:反射的核心类,反射所有的操作都是围绕该类来生成的。通过Class类,可以获取类的属性、方法等内容信息。
-
Field类:表示类的属性,可以获取和设置类中属性的值。
-
Method类:表示类中的方法,它可以用来获取类中的方法的信息,或者执行方法。
-
Constructor类:表示类的构造方法。
获取Class对象的方式
获取Class对象的三种方式对应着java代码在计算机中的三个阶段:
(1)【Source源代码阶段】 Class.forName("全类名")
:将字节码文件加载进内存,返回Class对象,多用于配置文件,将类名定义在配置文件中。读取文件,加载类。
(2)【Class类对象阶段】 类名.class
:通过类名的属性class获取,多用于参数的传递。
(3)【Runtime运行时阶段】对象.getClass()
:getClass()方法是定义在Objec类中的方法,多用于对象的获取字节码的方式。
结论:同一个字节码文件(*.class
)在一次程序运行过程中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个。
代码测试:
public class Person {
private String name;
private int age;
//省略构造方法、getter()、setter()、toString()
}
public class Demo {
public static void main(String[] args) throws ClassNotFoundException {
//方式一:Class.forName("全类名");//包名+类名
//Person自定义实体类
Class cls1 = Class.forName("com.coydone.fanshe.Person");
System.out.println("cls1 = " + cls1);
//方式二:类名.class
Class cls2 = Person.class;
System.out.println("cls2 = " + cls2);
//方式三:对象.getClass();
Person person = new Person("张三", 20);
Class cls3 = person.getClass();
System.out.println("cls3 = " + cls3);
// == 比较三个对象
System.out.println("cls1 == cls2 : " + (cls1 == cls2)); //true
System.out.println("cls1 == cls3 : " + (cls1 == cls3)); //true
//结论:同一个字节码文件(*.class)在一次程序运行过程中,
// 只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个。
}
}
//运行结果
//cls1 = class com.coydone.fanshe.Person
//cls2 = class com.coydone.fanshe.Person
//cls3 = class com.coydone.fanshe.Person
//cls1 == cls2 : true
//cls1 == cls3 : true
Class对象功能
在Java程序中使用反射的基本步骤如下:
1、导入java.lang.reflect.*
。
2、获取需要操作的类的Java.lang.Class对象。
3、调用Class的方法获取Field、Method等对象。
4、使用反射API进行操作。
获取功能
1、获取成员变量
Field[] getFields():获取所有public修饰的成员变量
Field getField(String name):获取指定名称的public修饰的成员变量
Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符
Class<?>getType()
int getModifiers()
String getName()
2、获取构造方法
Constructor<?>[] getConstructors():获取所有public修饰的构造器
Constructor<T> getConstructor(类<?>... parameterTypes):获取所有public修饰的构造器
Constructor<?>[] getDeclaredConstructors():获取所有的构造器
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) Constructor<T>:获取所有的构造器
3、获取成员方法
Method[] getMethods()
public int getModifiers()//Modifier.toString(mod); //还原修饰符
public Class<?>getReturnType()
public Class<?>[] getparameterTypes()
public Class<?>[] getExceptionTypes()
public static String toString(int mod)
Method getMethod(String name, 类<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
4、 获取全类名
String getName()
取得类所在的包
public Package getPackage()//得到一个类所在的包
public String getName()//得到名字
Field:成员变量
-
设置值:
void set(Object obj, Object value)
-
获取值:
get(Object obj)
-
忽略访问权限修饰符的安全检查
setAccessible(true)
:暴力反射
import java.lang.reflect.Field;
public class Demo02 {
public static void main(String[] args) throws Exception {
//0、获取Person的Class对象
Class personClass = Person.class;
//1、Field[] getFields()获取所有public修饰的成员变量
Field[] fields = personClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
//2.Field getField(String name)
Field id = personClass.getField("id");
//获取成员变量id的值 [也只能获取公有的,获取私有的或者不存在的字符会抛出异常]
Person p = new Person("张三", 20);
Object value = id.get(p);
System.out.println("value = " + value);
//设置属性id的值
id.set(p, 1111);
System.out.println(p);
//public int com.coydone.fanshe.Person.id
//value = 0
//Person{name='张三', age=20, id=1111}
Field[] declaredFields = personClass.getDeclaredFields();
for (Field filed : declaredFields) {
System.out.println(filed);
}
Field d = personClass.getDeclaredField("name"); //private String d;
Person p2 = new Person("张ww", 20);
//忽略访问权限修饰符的安全检查
d.setAccessible(true);//暴力反射
Object value2 = d.get(p2);
System.out.println("value2 = " + value2);
//private java.lang.String com.coydone.fanshe.Person.name
//private int com.coydone.fanshe.Person.age
//public int com.coydone.fanshe.Person.id
//value2 = 张ww
}
}
Constructor:构造方法
创建对象:T newInstance(Object... initargs)
注意:如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
修改测试的实体类
public class Person {
private String name;
private Integer age;
//无参构造函数
public Person() {
}
//单个参数的构造函数,且为私有构造方法
private Person(String name){
}
//有参构造函数
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
}
测试方法
import java.lang.reflect.Constructor;
public class Demo02 {
public static void main(String[] args) throws Exception {
/**
* 2. 获取构造方法们
* Constructor<?>[] getConstructors()
* Constructor<T> getConstructor(类<?>... parameterTypes)
*/
Class personClass = Person.class;
//Constructor<?>[] getConstructors()
Constructor[] constructors = personClass.getConstructors();
for (Constructor constructor : constructors) {
//Constructor 对象reflect包下的 import java.lang.reflect.Constructor;
System.out.println(constructor);
}
System.out.println("==========================================");
//获取无参构造函数 注意:Person类中必须要有无参的构造函数,不然抛出异常
Constructor constructor1 = personClass.getConstructor();
System.out.println("constructor1 = " + constructor1);
//获取到构造函数后可以用于创建对象
Object person1 = constructor1.newInstance();
//Constructor类内提供了初始化方法newInstance();方法
System.out.println("person1 = " + person1);
System.out.println("==========================================");
//获取有参的构造函数
//public Person(String name, Integer age) 参数类型顺序要与构造函数内一致,且参数类型为字节码类型
Constructor constructor2 = personClass.getConstructor(String.class, Integer.class);
System.out.println("constructor2 = " + constructor2);
//创建对象
//获取的是有参的构造方法,就必须要给参数
Object person2 = constructor2.newInstance("张三", 23);
System.out.println(person2);
System.out.println("=========================================");
//对于一般的无参构造函数,我们都不会先获取无参构造器之后在进行初始化。
//而是直接调用Class类内的newInstance()方法
Object person3 = personClass.newInstance();
System.out.println("person3 = " + person3);
//使用的 Class.forName("").newInstance;
//其本质上就是调用了类内的无参构造函数来完成实例化的
//故可以得出结论:
//在使用Class.forName("").newInstance;反射创建对象时,一定要保证类内有无参构造函数
}
}
Method:方法对象
执行方法:Object invoke(Object obj, Object... args)
获取方法名称:String getName();
修改测试的实体类
public class Person {
private String name;
private Integer age;
//无参构造函数
public Person() {
}
//有参构造函数
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
//无参方法
public void eat(){
System.out.println("eat...");
}
//重载有参方法
public void eat(String food){
System.out.println("eat..."+food);
}
}
/**
* 3. 获取成员方法们:
* Method[] getMethods()
* Method getMethod(String name, 类<?>... parameterTypes)
*/
import java.lang.reflect.Method;
public class Demo02 {
public static void main(String[] args) throws Exception {
Class personClass = Person.class;
//获取指定名称的方法
Method eat_method1 = personClass.getMethod("eat");
//执行方法
Person person = new Person();
Object rtValue = eat_method1.invoke(person);//如果方法有返回值类型可以获取到,没有就为null
//输出返回值 eat方法没有返回值,故输出null
System.out.println("rtValue = " + rtValue);
System.out.println("--------------------------------------------");
//获取有参的构造函数 有两个参数 第一个方法名 第二个参数列表 ,不同的参数是不同的方法(重载)
Method eat_method2 = personClass.getMethod("eat", String.class);
//执行方法
eat_method2.invoke(person,"饭");
System.out.println("============================================");
//获取方法列表
Method[] methods = personClass.getMethods();
for(Method method : methods){
//注意:获取到的方法名称不仅仅是我们在Person类内看到的方法
System.out.println(method); //继承下来的方法也会被获取到(当然前提是public修饰的)
}
}
}
getName()方法
getName()方法获取的方法名是仅仅就是方法名(不带全类名),且不带有参数列表。
import java.lang.reflect.Method;
public class Demo02 {
public static void main(String[] args) throws Exception {
Class personClass = Person.class;
Method[] methods = personClass.getMethods();
for(Method method : methods){
System.out.println(method);
//获取方法名
String name = method.getName();
System.out.println(name);
}
}
}
关于获取成员方法们的另外两个方法
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
method.setAccessible(true); //暴力反射
同之前的叙述一样,带有Declared关键字的方法这两个方法,可以获取到任意修饰符的方法。同样的提供了setAccessible(true);方法进行暴力反射。
综上说述:对于反射机制来说,在反射面前没有公有私有,都可以通过暴力反射解决。
获取类名
getClass()方法是Object类的方法,需要注意一点获取的类名是全类名(带有路径)
Class personClass = Person.class;
String className = personClass.getName();
System.out.println(className);//带有路径的全类名(包名+类名)
案例
写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。
实现
(1)配置文件,(2)反射
步骤
(1)将需要创建的对象的全类名和需要执行的方法定义在配置文件中
(2)在程序中加载读取配置文件
(3)使用反射技术来加载类文件进内存
(4)创建对象
(5)执行方法
代码实现
结构:
src
com.coydone.test
Person.java
ReflectTest.java
Student.java
Person类
package com.coydone.test;
public class Person {
public void eat(){
System.out.println("eat...");
}
}
Student类
package com.coydone.test;
public class Student {
public void study(){
System.out.println("learn...");
}
}
编写配置文件
以后我们在配置文件内看见全类名,就应该想到可能使用到了反射
在src下新建一个test.properties
,在文件中写如下内容
className = com.coydone.test.Person
methodName = eat
编写测试方法(模拟框架)
package com.coydone.test;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
public class ReflectTest {
public static void main(String[] args) throws Exception {
/**
* 前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法
* 即:拒绝硬编码
*/
//1.加载配置文件
//1.1创建Properties对象
Properties pro = new Properties();
//1.2加载配置文件,转换为一个集合
//1.2.1获取class目录下的配置文件 使用类加载器
ClassLoader classLoader = ReflectTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("test.properties");
pro.load(is);
//2.获取配置文件中定义的数据
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
//3.加载该类进内存
Class cls = Class.forName(className);
//4.创建对象
Object obj = cls.newInstance();
//5.获取方法对象
Method method = cls.getMethod(methodName);
//6.执行方法
method.invoke(obj);
}
}
运行结果
修改配置文件,再次运行
#将配置文件内的信息修改为Student类及类内的方法
className = com.coydone.test.Student
methodName = study
好处
我们这样做有什么好处呢,对于框架来说,是人家封装好的,我们拿来直接用就可以了,而不能去修改框架内的代码。但如果我们使用传统的new形式来实例化,那么当类名更改时我们就要修改Java代码,这是很繁琐的。修改Java代码以后我们还要进行测试,重新编译、发布等等一系列的操作。而如果我们仅仅只是修改配置文件,就来的简单的多,配置文件就是一个实实在在的物理文件。
此外使用反射还能达到解耦的效果,假设我们使用的是new这种形式进行对象的实例化。此时如果在项目的某一个小模块中我们的一个实例类丢失了,那么在编译期间就会报错,以导致整个项目无法启动。而对于反射创建对象Class.forName("全类名");
这种形式,我们在编译期需要的仅仅只是一个字符串(全类名),在编译期不会报错,这样其他的模块就可以正常的运行,而不会因为一个模块的问题导致整个项目崩溃。这就是Spring框架中IoC控制反转的本质。
评论区