目录
反射:框架设计的灵魂
在后期使用框架的时候,是否掌握反射关系不大,因为框架已经写好了;如果是自己开发一个框架,让别人使用,那么反射需要深入理解和掌握。同时,如果已经理解反射原理,那么可以更好的使用已有的框架。
框架:半成品软件。可以在框架的基础上进行软件开发,简化编码。
反射:将类的各个组成部分封装为其他对象,这就是反射机制。
好处:
1. 可以在程序运行过程中,操作这些对象。
2. 可以解耦,提高程序的可扩展性。
获取Class对象的方式
1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象。这个全类名是“包名.类名”。
* 多用于配置文件,将类名定义在配置文件中。读取文件,加载类。
2. 类名.class:通过类名的属性class获取。
* 多用于参数的传递。
3. 对象.getClass():getClass()方法在Object类中定义着。
* 多用于对象的获取字节码的方式。
* 结论:
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,即不论通过哪一种方式获取的Class对象都是同一个。
// 定义一个Person类
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 后面省略不写了
}
// 返回获取Class对象的方式
public class ReflectTest {
public static void main(String[] args) throws Exception {
// 1. Class.forName("全类名"),全类名=包名.类名
Class cls1 = Class.forName("demo23.Person");
System.out.println(cls1); // class demo23.Person
// 2. 类名.class
Class cls2 = Person.class;
System.out.println(cls2); // class demo23.Person
// 3. 对象.getClass()
Person person = new Person();
Class cls3 = person.getClass();
System.out.println(cls3); // // class demo23.Person
// 比较三个获取得到的对象是否同一个
System.out.println(cls1==cls2); // true
System.out.println(cls1==cls3); // true
}
}
Class对象功能
获取功能:
1. 获取成员变量们
* Field[] getFields() :获取所有public修饰的成员变量
* Field getField(String name) 获取指定名称的 public修饰的成员变量
* Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
* Field getDeclaredField(String name) 获取指定名称的成员变量,不考虑修饰符
2. 获取构造方法们
* Constructor<?>[] getConstructors() 获取所有public修饰的构造方法
* Constructor<T> getConstructor(类<?>... parameterTypes) 获取指定参数的 public修饰的成员方法
* Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) 获取所有的构造方法,不考虑修饰符
* Constructor<?>[] getDeclaredConstructors()
3. 获取成员方法们:
* Method[] getMethods() 获取所有public修饰的成员方法
* Method getMethod(String name, 类<?>... parameterTypes) 获取指定名称,该方法对应的参数的 public修饰的成员方法
* Method[] getDeclaredMethods() 获取所有的成员方法,不考虑修饰符
* Method getDeclaredMethod(String name, 类<?>... parameterTypes)
4. 获取全类名
* String getName()
1.class的成员变量们
首先通过上述class获取功的方法来获取成员变量们,然后进行下面的操作
Field:成员变量
* 操作:
1. 设置值
* void set(Object obj, Object value)
2. 获取值
* get(Object obj)
3. 忽略访问权限修饰符的安全检查
* setAccessible(true):暴力反射
// 定义一个Person类
public class Person {
private String name;
private byte age;
public int a; // public修饰
protected float b; // protected修饰
char c; // 默认修饰符
private boolean d; // private修饰
// 其余不重要的省略
}
// 获取class的成员变量,并对其操作
public class ReflectTest {
public static void main(String[] args) throws Exception {
// 0.获取class对象
Class personCls = Person.class;
// 1.获取成员变量
// 方法一:Field[] getFields() 获取所有public修饰的成员变量,其他修饰符不包含在里面
Field[] fields = personCls.getFields();
// 下面只打印了a变量,public int demo23.Person.a
for (Field field : fields) {
System.out.println(field);
}
// 方法二:Field getField(String name) 获取指定名称的 public修饰的成员变量
Field a = personCls.getField("a");
Person person = new Person();
// 1.1获取成员变量a的值
Object valueA = a.get(person);
System.out.println(valueA); // int默认初始化是0
// 1.2设置成员变量a的值
a.set(person,10);
System.out.println(person); // Person{name='null', age=0, a=10, b=0.0, c= , d=false} 因为字符类型c,默认为‘\u0000’ 打印不可见。
// 方法三、Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
Field[] declaredFields = personCls.getDeclaredFields();
// 打印了全部的变量
/*
private java.lang.String demo23.Person.name
private byte demo23.Person.age
public int demo23.Person.a
protected float demo23.Person.b
char demo23.Person.c
private boolean demo23.Person.d
*/
for (Field field : declaredFields) {
System.out.println(field);
}
// 方法四、Field getDeclaredField(String name) 获取指定名称的成员变量,不考虑修饰符
Field d = personCls.getDeclaredField("d");
// 忽视访问权限修饰符的安全检查,暴力反射。
d.setAccessible(true);
Object valueD = d.get(person);
System.out.println(valueD); // false 由于变量d是一个private变量,所以需要暴力反射再访问。否则会报错IllegalAccessException
}
}
2.class的构造方法们
首先通过上述class获取功的方法来获取成员变量们,然后进行下面的操作
Constructor:构造方法
* 创建对象:
* T newInstance(Object... initargs)
* 如果使用空参数构造方法创建对象,操作可以简化: Class对象的newInstance方法
* 忽略访问权限修饰符的安全检查:setAccessible(true):暴力反射
// 定义一个Person类
public class Person {
private String name;
public int a; // public修饰
protected float b; // protected修饰
private boolean d; // private修饰
public Person() {
}
public Person(String name, int a) {
this.name = name;
this.a = a;
}
public Person(float b, boolean d) {
this.b = b;
this.d = d;
}
// 后面的省略了
}
// 构造器对象的使用
public class ReflectTest {
public static void main(String[] args) throws Exception {
// 0.获取class对象
Class personCls = Person.class;
// 1.获取构造方法们
// 方法一:获取构造方法Constructor<T> getConstructor(类<?>... parameterTypes)
// 创建对象,有参数
Constructor constructor = personCls.getConstructor(float.class, boolean.class);
System.out.println(constructor); // public demo23.Person(float,boolean)
Object person = constructor.newInstance(10.0F, true);
System.out.println(person); // Person{name='null', a=0, b=10.0, d=true}
// 创建对象,无参数
Constructor constructor1 = personCls.getConstructor();
Object person1 = constructor1.newInstance();
System.out.println(person1); // Person{name='null', a=0, b=0.0, d=false}
// 无参数的时候,建议使用Class对象的newInstance方法
Object person2 = personCls.newInstance();
System.out.println(person2); // Person{name='null', a=0, b=0.0, d=false}
}
}
3.class的成员方法们
Method:方法对象
* 执行方法:
* Object invoke(Object obj, Object... args)
* 获取方法名称:
* String getName:获取方法名
// 定义一个Person类
public class Person {
private String name;
public int a; // public修饰
protected float b; // protected修饰
private boolean d; // private修饰
public void watch(){
System.out.println("前无古人,后无来者!");
}
// 方法的重载,有关的因素(1)参数个数不同。(2)参数类型不同。(3)参数的多类型顺序不同。
public void watch(String name){
System.out.println("前方隐隐约约来了一位丁香姑娘:"+name);
}
// 后面省略了
}
// 成员方法对象的使用
import java.lang.reflect.Method;
public class ReflectTest {
public static void main(String[] args) throws Exception {
// 0.获取class对象
Class personCls = Person.class;
// 1.获取成员方法们
// 获取指定名称的成员方法:Method getMethod(String name, 类<?>... parameterTypes) 获取指定名称,该方法对应的参数的 public修饰的成员方法
// 指定的方法无参数
Method watch1 = personCls.getMethod("watch");
// 执行
Person person = new Person();
watch1.invoke(person); // 前无古人,后无来者!
// 指定的方法有参数
Method watch2 = personCls.getMethod("watch", String.class);
watch2.invoke(person, "王昭君"); // 前方隐隐约约来了一位丁香姑娘:王昭君
// 获取所有public修饰的方法,不仅仅是对象的自己的方法,还包括了Object父类的一些方法
Method[] methods = personCls.getMethods();
for (Method method : methods) {
System.out.println(method); // 打印 public void demo23.Person.watch()...
// 获取方法对象的名字
String methodName = method.getName();
System.out.println(methodName); // 打印对应的方法名字watch...
}
}
}
4. class的全类名
String getName()
// 获取class对象的全类名
public class ReflectTest {
public static void main(String[] args) throws Exception {
// 0.获取class对象
Class personCls = Person.class;
// 1.获取class对象的全类名:包.类名
String className = personCls.getName();
System.out.println(className); // demo23.Person 是一个全类名
}
}
为什么框架要使用反射
以前要使用方法,则创建对象,然后调用对象方法。但是,如果是一个框架类,那么这样存在一个弊端:既然是一个框架,那么是一个半成品的软件,代码是提前写好的、不允许修改。框架的前提就是:不能改变任何代码,但是可以创建类一类的对象,可以执行任意方法。
案例:
* 需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
* 实现的技术:
1. 配置文件
2. 反射
* 步骤:
1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
2. 在程序中加载读取配置文件
3. 使用反射技术来加载类文件进内存
4. 创建对象
5. 执行方法
如果框架要改,代码要改,那么需要重新测试,重新上线。该配置文件的方式能使得程序的拓展性更强。
当一个配置文件出现了全类名,那么这个配置文件用到了反射技术。
//配置文件pro.properties
className=demo23.Person // 配置文件中可以修改类和方法等等,而不需要修改框架中的代码
methodName=watch
// 定义一个Person类
public class Person {
private String name;
public int a; // public修饰
protected float b; // protected修饰
private boolean d; // private修饰
public void watch(){
System.out.println("前无古人,后无来者!");
}
// 方法的重载,有关的因素(1)参数个数不同。(2)参数类型不同。(3)参数的多类型顺序不同。
public void watch(String name){
System.out.println("前方隐隐约约来了一位丁香姑娘:"+name);
}
public Person() {
}
}
// 反射的魅力--框架的灵魂
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文件
// 1.2.创建Properties对象,因为可以加载properties文件形成一个集合。
Properties properties = new Properties();
// 1.3加载配置文件,转换为一个集合
// 1.3.1获取字节码文件对应的类加载器,是这个类加载器将字节码加载进内存的。
ClassLoader classLoader = ReflectTest.class.getClassLoader(); // ReflectTest.class表示当前类的字节码文件
// 1.3.2.既然类加载器可以找到class目录下的class文件,那么也可以找到同目录下的配置文件
// classLoader.getResource(String name)方法返回的是URL即资源的完整路径
System.out.println(classLoader.getResource("pro.properties")); // file:/C:/Users/jupy/IdeaProjects/basic_code/out/production/code01/pro.properties
// classLoader.getResourceAsStream(String name)方法返回的是字节流InputStream
InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properties");
System.out.println(resourceAsStream); // java.io.BufferedInputStream@677327b6
// 1.3.3.加载配置文件的字节流
properties.load(resourceAsStream);
// 2.获取配置文件中定义的数据
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
// 3.使用反射技术,加载该类进内存
Class cls = Class.forName(className);
// 4.创建对象
Object obj = cls.newInstance();
// 5.获取方法对象并且执行方法
Method method1 = cls.getMethod(methodName);
method1.invoke(obj); // 前无古人,后无来者!
// 如果所有的方法都不需要输入参数,那么该框架可以不修改,这里只是为了测试其他方法
Method method2 = cls.getMethod(methodName, String.class);
method2.invoke(obj,"高圆圆"); // 前方隐隐约约来了一位丁香姑娘:高圆圆
}
}