通过外部文件配置,在不修改源码情况下,来控制程序,也符合设计模式的ocp原则(不修改源码,且能扩容功能),这样的需求在框架里特别多。
常用内容:反射的定义,使用场景,实现反射用到的类,优缺点,实现方式。
反射机制
反射机制可以完成的功能
反射相关类
程序加载的三个阶段
反射的优点和缺点
反射的优点
- 可扩展性:可以动态的创建和使用对象(Spring框架的底层原理)。
透明性:对于任意一个类,都能够知道这个类的所有字段和方法,并且使用他们。
反射的缺点
性能开销:反射涉及了动态类型的解析,操作的效率要比 非反射操作低得多。
- 安全限制:使用反射技术要求程序必须在一个没有安全限制的环境中运行。
内部暴露:由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有属性和方法),使用反射可能导致代码功能失调,破坏抽象性,可移植性等。
优化方法
xxx.setAccessible(true); //在反射调用方法时,取消访问检查
Class类
基本介绍
public class Class01 {
public static void main(String[] args) throws ClassNotFoundException {
Class a1 = Class.forName("reflection.Car");
Class a2 = Class.forName("reflection.Car");
System.out.println(a1.hashCode()); //1134712904
System.out.println(a2.hashCode()); //1134712904
//hashCode相同,说明Class只被系统创建一次
}
}
常用方法
public class Class01 {
public static void main(String[] args) throws ... {
//1. 类的完整路径
String classAllPath = "reflection.Car";
//2. 获取到Car类 对应的Class对象,<?>表示不确定的Java类型
Class<?> cls = Class.forName(classAllPath);
System.out.println(cls); //显示哪个类的Class对象 class reflection.Car
System.out.println(cls.getClass());//输出cls的运行类型 class java.lang.Class
//3. 得到包名
System.out.println(cls.getPackage().getName()); //reflection
//4. 得到全类名
System.out.println(cls.getName()); //reflection.Car
//5. 通过cls创建对象实例
Car car = (Car)cls.newInstance();
//6. 通过反射获取属性 brand
Field brand = cls.getField("brand"); //宝马
System.out.println(brand.get(car));
//7. 通过反射给属性赋值
brand.set(car,"奔驰");
System.out.println(brand.get(car));
//8. 得到所有的属性
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
}
}
获取Class对象的方法
//1. Class.forName
String classAllPath = "reflection.Car";
Class<?> cls1 = Class.forName(classAllPath);
System.out.println(cls1); //class reflection.Car
//2. 类名.class
Class cls2 = Car.class;
System.out.println(cls2); //class reflection.Car
```java //3. 对象.getClass() Car car = new Car(); Class cls3 = car.getClass(); System.out.println(cls3); //class reflection.Car
//4. 通过类加载器来获取类的Class对象 //(1) 先得到类加载器 ClassLoader classLoader = car.getClass().getClassLoader(); //(2) 通过类加载器得到Class对象 Class cls4 = classLoader.loadClass(“reflection.Car”); System.out.println(cls4); //class reflection.Car
![image.png](https://cdn.nlark.com/yuque/0/2022/png/23175776/1641804514889-2ba054de-4cac-4c94-8edb-2fbadf810e8f.png#clientId=u6c57bb3c-32f1-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u4a0aaa3c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=455&originWidth=1517&originalType=url&ratio=1&rotation=0&showTitle=false&size=568369&status=done&style=none&taskId=ucb0eb0ef-6efc-4f74-8a59-36ab7d1c832&title=)
```java
//5. 基本数据类型
Class<Integer> integerClass = int.class;
Class<Boolean> booleanClass = boolean.class;
System.out.println(integerClass); //int
//6. 基本数据类型对应的包装类
Class<Integer> type = Integer.TYPE;
Class<Boolean> type1 = Boolean.TYPE;
System.out.println(type); //int
System.out.println(integerClass.hashCode()); //1766822961
System.out.println(type.hashCode()); //1766822961
哪些类型有Class对象
类加载
静态加载与动态加载
静态加载:编译时加载需要的类,如果没有定义该类则报错,依赖性太强。
动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类也不会报错,降低了依赖性。
public static void main(String[] args) throws ... {
//new Dog() 是静态加载,因此必须编写Dog类
Dog dog = new Dog();
//Person类是动态加载,因此没有编写Person类编译器也不会报错,只有当动态加载该类时才会报错 Class cls = Class.forName("reflection.Person");
}
类加载时机
类加载过程图
类加载的各个阶段
加载阶段
连接阶段——验证
连接阶段——准备
//n1 是实例属性,不是静态变量,因此在准备阶段不会分配内存
public int n1 = 10;
//n2 是静态变量,分配内存。默认初始化为0,而不是20
public static int n2 = 20;
//n3 是常量,分配内存。它一旦被赋值就不变,因此直接初始化为30
public static final int n3 = 30;
连接阶段——解析
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。而直接引用就是指向目标的指针等。
初始化阶段
对出现的顺序的理解:对下面这段代码: static int num = 100; 在静态代码块上方,因此会被 num = 300覆盖。
class B{
public static void main(String[] args) {
System.out.println(B.num); // 300
}
static int num = 100; //在静态代码块前出现
static{
System.out.println("B 静态代码块被执行");
num = 300;
}
}
如果按照下面这样写,就不会被 num = 300覆盖,最终num = 100。
static{
System.out.println("B 静态代码块被执行");
num = 300;
}
static int num = 100; //在静态代码块后出现
通过反射获取类的结构信息
java.lang.Class类
java.lang.reflect.Field类
java.lang.reflect.Method类
java.lang.reflect.Constructor类
通过反射创建对象
方式一:调用类中的public修饰的无参构造器(Java11中已弃用)。
方式二:调用类中的指定构造器(如果该构造器是private,要用到暴力破解)。
Class类相关方法
Constructor类相关方法
public static void main(String[] args) throws ...{
//1. 先获取到User类的Class对象
Class<?> userClass = Class.forName("reflection.User");
//2. 通过public无参构造器创建实例
User user1= (User) userClass.newInstance(); //该方法已弃用
System.out.println(user1);
//3. 用public有参构造器创建实例
Constructor<?> constructor = userClass.getConstructor(String.class);//()里是构造器对应的参数
User user = (User)constructor.newInstance("ss");
System.out.println(user); //不是同一个对象
//4. 通过非public的有参构造器创造实例
//得到private的构造器方法,要用 getDeclaredConstructor
Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(int.class, String.class);
//创建实例,要用到 暴力破解
declaredConstructor.setAccessible(true);//关闭检查
User user2 = (User)declaredConstructor.newInstance(100,"sssss");
System.out.println(user2);
}
通过反射访问类中的成员
class User{
private int age;
private String name;
public void say(){
System.out.println("say方法被调用");
}
public void say(String name){
System.out.println("带有参数的say方法被调用");
}
public static void say(String name,int age){
System.out.println("静态say方法被调用");
}
private void hi(){
System.out.println("private方法被调用");
}
}
public static void main(String[] args) throws ... {
//1. 先获取到User类的Class对象
Class<?> userClass = Class.forName("reflection.User");
//2. 得到一个对象
Object o = userClass.newInstance();
//3. 获取public方法(不带参数)
Method say = userClass.getMethod("say");
say.invoke(o);
//4. 获取public方法(带参数)
Method say1 = userClass.getMethod("say", String.class); //要把参数的Class写入
say1.invoke(o,"aaa");
//5. 获取static方法
Method say2 = userClass.getMethod("say", String.class, int.class);
//因为 hi2对应的是static方法,因此不用写对象(写也可以)
say2.invoke(null,"aaa",12);
//6. 获取private方法
Method hi = userClass.getDeclaredMethod("hi");
hi.setAccessible(true); //爆破
hi.invoke(o);
}
如果方法有返回值,统一返回Object,但是它的运行类型(getClass)和方法定义的返回类型一致。