反射复习

能干嘛

​ 当我们创建一个对象时,需要事先在源码中进行编写,例如:new Object(),但如果未来发生变化,我就不得不在源码中进行修改,这也违反了开闭原则,我们能不能仅修改配置文件就可以调整系统中需要创建的类。比如说我在配置文件写:

classPathName=org.Mysql.SQLBuilder
mathod=getSqlSession

​ 我想未来我的系统可以读取该配置文件,知道需要的是一个org.Mysql.SQLBuilder类,并且调用getSqlSession方法。等到未来如果修改成:

classPathName=org.Oracle.SQLBuilder
mathod=getSqlSessions

​ 那系统也能动态的感知到现在需要的是org.Oracle.SQLBuilder,并要调用getSqlSessions方法。在使用反射前我们是无法解决的,因为所有使用的类,在编译期就固定了。所以引出了反射技术。也就是为了在运行期,可动态获取类信息或者创建对象。

​ 这在我们学习框架例如Spring,Mybatis时就经常使用,比如我想动态的获取@Table(name="my_db")注解内的内容,就可以通过反射来进行获取。

具体功能:

  1. 在运行时判断任意一个对象所属的类 (

  2. 在运行时构造任意一个类对象

  3. 在运行时得到任意一个类所具有的的成员方法和方法

  4. 在运行时调用任意一个对象的成员变量和方法

  5. 生成动态代理

是什么

​ 反射机制允许程序在执行期间借助Reflection API,获取任何类的内部信息(比如:成员变量,构造器,成员方法等),并能操作属性和方法。

​ 当JVM加载完类后,会在堆中产生一个Class类型的对象(一个类只有一个Class对象),这个类对象包含了类的完整结构信息。

Java反射机制原理图

image-20221203151322560

类加载过程

image-20221203155444189

image-20221203155504141

​ 简单来说就是我们写成的源码,在被JVM加载后,会对每个类生成唯一的Class对象,每当我们实例化一个对象时,这个对象的对象头都有一个指针指向对象所属的唯一的Class对象。

​ 当我们获取到Class对象时,我们可以知道Class成员变量,方法,构造器,并进行任意的使用。

其中最主要的几个类:

  1. Java.lang.Class 表示一个类
  2. Java.lang.reflect.Method 表示方法
  3. Java.lang.reflect.Field 表示成员变量
  4. Java.lang.reflect.Constructor 表示构造器

​ 其中Method,Field,Constructor都有getModifiers()以int形式返回修饰符(private,protect,public)

0: 默认
1: public
2: private
4: protected
8: static
16: fianl

​ public(1) + static(8) = 9 这样就可以判断出一些信息。

怎么用

@Test
public void test3() {
  	// 找到String的类对象
    Class<String> aClass = String.class;
  	// 获取字段 public
    Field[] fields = aClass.getFields();
  	// 获取方法 public
    Method[] methods = aClass.getMethods();
  	// 获取构造器 public
    Constructor<?>[] constructors = aClass.getConstructors();
  	// 所有修饰符的都可以获取到
    aClass.getDeclaredFields();
    aClass.getDeclaredMethods();
    aClass.getDeclaredConstructors();
}

反射的优缺点:

  1. 优点:可以动态创建和使用对象
  2. 缺点:反射基本是解释执行,对执行速度有影响

我们可代码验证

public class demo {
    @Test
    public void test1() throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        int time = 10000000;
        // 正常方式:
        Cat cat = new Cat();
        long start = System.currentTimeMillis();
        for (int i = 0; i < time; i++) {
            cat.hi();
        }
        long end = System.currentTimeMillis();
        System.out.println("正常调用:"+ (end - start));
        // 反射方式:
        Class<? extends Cat> aClass = cat.getClass();
        Cat cat1 = aClass.newInstance();
        Method hi = aClass.getMethod("hi");
        start = System.currentTimeMillis();
        for (int i = 0; i < time; i++) {
            hi.invoke(cat1);
        }
        end = System.currentTimeMillis();
        System.out.println("反射调用:"+ (end - start));
        
        // 结果: 
        // 正常调用:9
        // 反射调用:84
    }
}
class Cat {
    public void hi() {}
}

​ Method和Field和Constructor对象都有setAccessible()方法,setAccessible作用是启动和禁止访问安全检查的开关。当setAccessible(true)表示反射的对象在使用时取消访问检查,提高反射的效率,为false则表示执行访问检查。当我们修改上述代码:hi.setAccessible(true);,最终执行速度可以快一倍左右。

​ 并且当我们访问private修饰的变量,方法,构造器时,如果不设置setAccessible()会爆出:java.lang.IllegalAccessException; 如果设置则可正常访问。

Class类

image-20221203154354105

  1. Class也是类,继承Object。
  2. Class类不是new出来的,而是系统创建的
  3. 某个类的Class类对象,在内存只有一份,因为类只加载一次 (如果多线程同时进行同一个类加载,会导致阻塞)
  4. 每个类的实例都知道自己是哪个Class实例的
  5. 通过Class对象可以完整的获取一个类的完整结构
  6. Class对线是存放在堆的
  7. 类的字节码二进制数据,是放在方法区的,也称为元数据。

Class类的常用方法:

image-20221203155854410

如何获取Class类

  1. 已经知道全类名:Class.forName("className"),常用语配置文件读取,加载类
  2. 已经知道具体的类:Class clas = Cat.class,性能最好,一般用在参数传递
  3. 已经知道类的实例:Class clas = cat.getClass()
  4. 包装类型:Class i = Inteager().Type
  5. 基本数据类型:int.class

哪些类型有Class对象

1.2. 接口
3. 数组
4. 枚举
5. 注解
6. 基本数据类型
7. void

最终测试

测试1:

​ 定义PrivateTest类,私有属性name,属性值为hellokitty

  1. 提供getName的公有方法
  2. 利用Class类获取私有的name属性,修改属性值,并调用getName方法,打印name属性
public class demo {
    @Test
    public void test1() throws NoSuchFieldException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<PrivateTest> aClass = PrivateTest.class;
        PrivateTest instance = aClass.newInstance();
        Field name = aClass.getDeclaredField("name");
        name.setAccessible(true);
        name.set(instance,"wangCai");
        Method getName = aClass.getMethod("getName");
        System.out.println(getName.invoke(instance));
    }
}
class PrivateTest {
    private String name = "hellokitty";

    public String getName() {
        return name;
    }
}

测试2:

  1. 反射获取File类的Class对象
  2. 在控制台打印File类的所有构造器
  3. 通过newInstance创建File对象,并创建一个本地文件
public class demo {
    @Test
    public void test1() throws NoSuchFieldException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, ClassNotFoundException, IOException {
        Class<?> aClass = Class.forName("java.io.File");
        Constructor<?>[] constructors = aClass.getDeclaredConstructors();
        System.out.println(Arrays.toString(constructors));
        Constructor<?> constructor = aClass.getConstructor(String.class);
        File file = (File) constructor.newInstance("doc.txt");
        file.createNewFile();
    }
}