你必须了解的Java:反射
首先,反射是框架设计的灵魂 ,这句话的分量,读者自行体会。
1、几个概念:
- 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码;所谓框架,就是能够适应所有的情况,情况的不同,只需在配置文件中进行修改,而不是在原代码(这个指的框架)中修改;
- 反射:将类的各个组成部分封装为其他对象,这就是反射机制。
- 好处:
-
- 可以在程序运行过程中,操作这些对象。idea 就是在程序的运行过程中,运用了反射的机制。 比如:定义了一个字符串,结果字符串的方法就已经可以选取调用了。·因为在这个过程中,idea是一边写,一边编译,将字符串编译为字节码文件,并且已经将字节码文件通过ClassLoader加载进了内存中;
-
2. **可以解耦,提高程序的可扩展性。**
-
2、java在计算机中经历的三个阶段:
java代码 ------>javac进行编译------>字节码文件.class------>classLoader类加载器------>在堆中创建对象 new someoneClass
source源代码阶段 class类对象阶段 RUNTIME运行时阶段
3、反射中的对象:
1、成员变量 Field[] field
2、构造方法 Constructor[]
3、成员方法 Method[] method
4、获取Class对象的方式
-
Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象
多用于配置文件,将类名定义在配置文件中,配置文件中写的都是全类名。读取文件,加载类; -
通过类名属性class获得:( 多用于参数的传递)
// ClassName.class 不执行静态块和不执行动态块儿
Class<?> class = ClassName.class;
- 对象.getClass() 封装在object中:getClass()方法在Object类中定义着。
多用于对象的获取该类的 字节码文件 的方式
注意: 前两个是直接根据类名获取class文件 区别是全类名 或者类名(与绝对路径和相对路径相似)第三个是需要先创建类的对象,再有对象获取类的字节码文件。
结论: 三种获取的方式都是同一个字节码文件(*.class)。在一次程序运行过程中,一个类只会被加载一次,所以不论通过哪一种方式获取的Class对象都是同一个。
静态代码块与动态代码块在 获取class文件中的应用:
首先定义一个测试类:
public class ClassTest {
static{
// 静态代码块
System.out.println("静态代码块儿...");
}
{
// 动态代码块
System.out.println("动态构造块儿...");
}
public ClassTest(){
// 类中的构造方法
System.out.println("构造方法...");
}
}
使用第1种方法:
public class MainTest {
public static void main(String[] args) {
try {
Class<?> calss = Class.forName("com.souche.lease.admin.mytest.reflect.ClassTest");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
/* 打印结果是:
* 静态代码块儿...
* 说明Class.forName("类名全路径")执行静态块但是不执行动态块儿(需要异常处理)
*/
使用第2种方法:
public class MainTest {
public static void main(String[] args) {
Class<?> calss = ClassTest.class;
}
}
/**
* 打印结果是:
* 什么都没打印
* 说明ClassName.class不执行静态块和不执行动态块儿
*/
使用第3种方法:
public class MainTest {
public static void main(String[] args) {
Class<?> calss = new ClassTest().getClass();
}
}
/**
* 打印结果是:
* 静态代码块儿...
动态构造块儿...
构造方法...
* 说明对象.getClass()执行静态块也执行动态块儿
*/
关于获取class文件方法不同的总结:
第1种方法:类字面常量使得创建Class对象的引用时不会自动地初始化该对象,而是按照之前提到的加载,链接,初始化三个步骤,这三个步骤是个懒加载的过程,不使用的时候就不加载。
第2种方法:Class类自带的方法。
第3种方法:是所有的对象都能够使用的方法,因为getClass()方法是Object类的方法,所有的类都继承了Object,因此所有类的对象也都具有getClass()方法。
建议:使用类名.class,这样做即简单又安全,因为在编译时就会受到检查,因此不需要置于try语句块中,并且它根除了对forName()方法的调用,所以也更高效。
补充注意:静态块仅在类加载时执行一次,若类已加载便不再重复执行;而动态构造块在每次new对象时均会执行。
补充:生成对象四种方式
请参见blog: https://blog.youkuaiyun.com/qq_39817135/article/details/101313225
5、Class对象功能( 就是获取class文件之后,拿它来做什么。)
1. 获取 成员变量们 field
获取了成员变量之后,就可以将获取的成员变量进行设置、获取(也就是get和set)原类中成员变量的值。从而让其达到不创建对象就可以操作类中成员变量Field,构造方法Constructor,成员方法Method三个大的类对象;
Field[] getFields(): // 获取所有 public 修饰的成员变量
Field getField(String name) // 获取的指定的成员变量 获取指定名称的 public修饰的成员变量
Field[] getDeclaredFields() // 获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name) //获取的指定的成员变量
Field[] getDeclaredFields(); //获取所有的成员变量,不考虑修饰符
2. 举例说明 成员变量类对象 的获取,并设置、获取 成员变量类对象中的值
// e.g. 成员变量类对象 的获取,并设置、获取 成员变量类对象中的值
Field[] declaredFields = personClass.getDeclaredFields();
// 获取了所有的成员变量,并且将其放在了数组中;并可以将其遍历:
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
// 获取指定的成员变量,如果成员变量是私有的,那么需要将获取权限 即暴力反射获得权限
Field getDeclaredField(String name)
Field d = personClass.getDeclaredField("d");
// 成员变量类对象d 就可以进行d.get();d.set();
// 忽略访问权限修饰符的安全检查
d.setAccessible(true); //暴力反射
d.set(oo, "wangwu")
Object value2 = d.get(p);
System.out.println(value2);
3、获取构造方法们 :之后就创建对象
Constructor<?>[] getConstructors() //获取的是空参构造
Constructor<T> getConstructor(类<?>... parameterTypes)
// 获取的是有参构造
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
//Constructor<T> getConstructor(类<?>... parameterTypes)
Constructor constructor = personClass.getConstructor(String.class, int.class);
//这里返回得到的是 构造器
//这里传递的参数
//String.class,int.class是原构造方法中参数类型的class对象
System.out.println(constructor);
/**创建对象****重要:constructor.newInstance("张三", 23)
**当然也可以利用空参构造,创建对象:但是提供了比较简单的方法:
**直接跳过获取构造器,直接使用类名就可以利用空参构造的创建对象:
** Class对象的newInstance方法
** personClass.newInstance()*/
Object person = constructor.newInstance("张三", 23);
System.out.println(person);
4、 获取成员方法们:之后就执行方法
Method[] getMethods()
Method getMethod(String name, 类<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
//获取指定名称的方法 其实是总的方法类对象
Method eat_method = personClass.getMethod("eat");
// 这里是无参方法,可以不用传递参数类型的对象
Person p = new Person();
//获取到成员方法之后,需要执行方法,关键词:.invoke(p)
//获取的法类对象来执行 需要传递的是一个对象,以及该方法执行所需的参数类对象
eat_method.invoke(p); // 这里是无参方法,可以不用传递参数类型的对象
// 有参方法举例并说明:
Method eat_method = personClass.getMethod("eat",String.class,int.class,...);
eat_method.invoke(p,"abcdef",6);
//获取所有public修饰的方法
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method);
String name = method.getName();
//获取所有的方法名,
//这里获取的方法名不仅仅是方法中的public修饰的方法名,
//还有其继承Object中的方法
System.out.println(name);
//method.setAccessible(true);
}
}
/* Field:成员变量
获取了成员变量之后,就可以用将获取的成员变量进行设置、获取原类中成员变量的值。
这样就可以 不通过构造方法,对目标类(最开始的类)进行获取或者设置成员变量的值。*/
// 操作:
//1. 设置值
* void set(Object obj, Object value)
//2. 获取值
* get(Object obj)
//3. 忽略访问权限修饰符的安全检查
* setAccessible(true): 暴力反射(私有的成员变量都可以访问)
// Constructor:构造方法
// 创建对象:
T newInstance(Object... initargs)
// 如果使用空参数构造方法创建对象,操作可以简化:
Class对象的newInstance方法
// Method:方法对象
// 执行方法:
Object invoke(Object obj, Object... args)
// 获取方法名称:
String getName:获取方法名
面试题:
需求:
写一个"框架",不能改变该类的任何代码的前提下,
可以帮我们创建任意类的对象,并且执行其中任意方法。
回答思路
实现:
-
配置文件 properties
-
反射(如果配置文件中 出现了全类名,绝大部分情况就是运用了反射)
步骤:
- 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
- 在程序中加载读取配置文件
- 使用反射技术来加载类文件进内存
- 创建对象
- 执行方法