通过外部文件配置,在不修改源码情况下,来控制程序,也符合设计模式的ocp原则(不修改源码,且能扩容功能),这样的需求在框架里特别多。
常用内容:反射的定义,使用场景,实现反射用到的类,优缺点,实现方式。

反射机制

image.png

反射机制可以完成的功能

image.png

反射相关类

image.png

程序加载的三个阶段

image.png
Class类对象在堆中,其对应生成的对象也在堆中。

反射的优点和缺点

反射的优点

  1. 可扩展性:可以动态的创建和使用对象(Spring框架的底层原理)。
  2. 透明性:对于任意一个类,都能够知道这个类的所有字段和方法,并且使用他们。

    反射的缺点

  3. 性能开销:反射涉及了动态类型的解析,操作的效率要比 非反射操作低得多。

  4. 安全限制:使用反射技术要求程序必须在一个没有安全限制的环境中运行。
  5. 内部暴露:由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有属性和方法),使用反射可能导致代码功能失调,破坏抽象性,可移植性等。

    优化方法

    image.png

    1. xxx.setAccessible(true); //在反射调用方法时,取消访问检查

    Class类

    基本介绍

    image.png

    1. public class Class01 {
    2. public static void main(String[] args) throws ClassNotFoundException {
    3. Class a1 = Class.forName("reflection.Car");
    4. Class a2 = Class.forName("reflection.Car");
    5. System.out.println(a1.hashCode()); //1134712904
    6. System.out.println(a2.hashCode()); //1134712904
    7. //hashCode相同,说明Class只被系统创建一次
    8. }
    9. }

    常用方法

    image.png

    1. public class Class01 {
    2. public static void main(String[] args) throws ... {
    3. //1. 类的完整路径
    4. String classAllPath = "reflection.Car";
    5. //2. 获取到Car类 对应的Class对象,<?>表示不确定的Java类型
    6. Class<?> cls = Class.forName(classAllPath);
    7. System.out.println(cls); //显示哪个类的Class对象 class reflection.Car
    8. System.out.println(cls.getClass());//输出cls的运行类型 class java.lang.Class
    9. //3. 得到包名
    10. System.out.println(cls.getPackage().getName()); //reflection
    11. //4. 得到全类名
    12. System.out.println(cls.getName()); //reflection.Car
    13. //5. 通过cls创建对象实例
    14. Car car = (Car)cls.newInstance();
    15. //6. 通过反射获取属性 brand
    16. Field brand = cls.getField("brand"); //宝马
    17. System.out.println(brand.get(car));
    18. //7. 通过反射给属性赋值
    19. brand.set(car,"奔驰");
    20. System.out.println(brand.get(car));
    21. //8. 得到所有的属性
    22. Field[] fields = cls.getFields();
    23. for (Field field : fields) {
    24. System.out.println(field.getName());
    25. }
    26. }
    27. }

    获取Class对象的方法

    image.png

    1. //1. Class.forName
    2. String classAllPath = "reflection.Car";
    3. Class<?> cls1 = Class.forName(classAllPath);
    4. System.out.println(cls1); //class reflection.Car
    5. //2. 类名.class
    6. Class cls2 = Car.class;
    7. System.out.println(cls2); //class reflection.Car

    image.png ```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

  1. ![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=)
  2. ```java
  3. //5. 基本数据类型
  4. Class<Integer> integerClass = int.class;
  5. Class<Boolean> booleanClass = boolean.class;
  6. System.out.println(integerClass); //int
  7. //6. 基本数据类型对应的包装类
  8. Class<Integer> type = Integer.TYPE;
  9. Class<Boolean> type1 = Boolean.TYPE;
  10. System.out.println(type); //int
  11. System.out.println(integerClass.hashCode()); //1766822961
  12. System.out.println(type.hashCode()); //1766822961

哪些类型有Class对象

image.png
image.pngimage.png

类加载

静态加载与动态加载

静态加载:编译时加载需要的类,如果没有定义该类则报错,依赖性太强。
动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类也不会报错,降低了依赖性。

  1. public static void main(String[] args) throws ... {
  2. //new Dog() 是静态加载,因此必须编写Dog类
  3. Dog dog = new Dog();
  4. //Person类是动态加载,因此没有编写Person类编译器也不会报错,只有当动态加载该类时才会报错 Class cls = Class.forName("reflection.Person");
  5. }

类加载时机

image.png

类加载过程图

image.png

image.png

类加载的各个阶段

加载阶段

image.png

连接阶段——验证

image.png

连接阶段——准备

image.png

  1. //n1 是实例属性,不是静态变量,因此在准备阶段不会分配内存
  2. public int n1 = 10;
  3. //n2 是静态变量,分配内存。默认初始化为0,而不是20
  4. public static int n2 = 20;
  5. //n3 是常量,分配内存。它一旦被赋值就不变,因此直接初始化为30
  6. public static final int n3 = 30;

连接阶段——解析

image.png
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。而直接引用就是指向目标的指针等。

初始化阶段

image.png
对出现的顺序的理解:对下面这段代码: static int num = 100; 在静态代码块上方,因此会被 num = 300覆盖。

  1. class B{
  2. public static void main(String[] args) {
  3. System.out.println(B.num); // 300
  4. }
  5. static int num = 100; //在静态代码块前出现
  6. static{
  7. System.out.println("B 静态代码块被执行");
  8. num = 300;
  9. }
  10. }

如果按照下面这样写,就不会被 num = 300覆盖,最终num = 100。

  1. static{
  2. System.out.println("B 静态代码块被执行");
  3. num = 300;
  4. }
  5. static int num = 100; //在静态代码块后出现

通过反射获取类的结构信息

java.lang.Class类

image.png

java.lang.reflect.Field类

image.png

java.lang.reflect.Method类

image.png
java.lang.reflect.Constructor类
image.png

通过反射创建对象

方式一:调用类中的public修饰的无参构造器(Java11中已弃用)。
方式二:调用类中的指定构造器(如果该构造器是private,要用到暴力破解)。

Class类相关方法
image.png
Constructor类相关方法
image.png

  1. public static void main(String[] args) throws ...{
  2. //1. 先获取到User类的Class对象
  3. Class<?> userClass = Class.forName("reflection.User");
  4. //2. 通过public无参构造器创建实例
  5. User user1= (User) userClass.newInstance(); //该方法已弃用
  6. System.out.println(user1);
  7. //3. 用public有参构造器创建实例
  8. Constructor<?> constructor = userClass.getConstructor(String.class);//()里是构造器对应的参数
  9. User user = (User)constructor.newInstance("ss");
  10. System.out.println(user); //不是同一个对象
  11. //4. 通过非public的有参构造器创造实例
  12. //得到private的构造器方法,要用 getDeclaredConstructor
  13. Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(int.class, String.class);
  14. //创建实例,要用到 暴力破解
  15. declaredConstructor.setAccessible(true);//关闭检查
  16. User user2 = (User)declaredConstructor.newInstance(100,"sssss");
  17. System.out.println(user2);
  18. }

通过反射访问类中的成员

image.png

  1. class User{
  2. private int age;
  3. private String name;
  4. public void say(){
  5. System.out.println("say方法被调用");
  6. }
  7. public void say(String name){
  8. System.out.println("带有参数的say方法被调用");
  9. }
  10. public static void say(String name,int age){
  11. System.out.println("静态say方法被调用");
  12. }
  13. private void hi(){
  14. System.out.println("private方法被调用");
  15. }
  16. }
  1. public static void main(String[] args) throws ... {
  2. //1. 先获取到User类的Class对象
  3. Class<?> userClass = Class.forName("reflection.User");
  4. //2. 得到一个对象
  5. Object o = userClass.newInstance();
  6. //3. 获取public方法(不带参数)
  7. Method say = userClass.getMethod("say");
  8. say.invoke(o);
  9. //4. 获取public方法(带参数)
  10. Method say1 = userClass.getMethod("say", String.class); //要把参数的Class写入
  11. say1.invoke(o,"aaa");
  12. //5. 获取static方法
  13. Method say2 = userClass.getMethod("say", String.class, int.class);
  14. //因为 hi2对应的是static方法,因此不用写对象(写也可以)
  15. say2.invoke(null,"aaa",12);
  16. //6. 获取private方法
  17. Method hi = userClass.getDeclaredMethod("hi");
  18. hi.setAccessible(true); //爆破
  19. hi.invoke(o);
  20. }

如果方法有返回值,统一返回Object,但是它的运行类型(getClass)和方法定义的返回类型一致。