Java - 反射

75

介绍:

是一门Java提供的专门技术, 这门技术让 Java 成为一门准动态语言

存在性能问题, 会占用额外内存, 速度也会较慢

一个类在内存中只有一个 Class 对象

一个类被加载后, 类的整个结构都会封装在 Class 对象中

Class 类:

class 的类, 所有类都指向了 Class

包含了类的所有信息

  • Class 只能由系统创建
  • 一个加载的类在JVM中只会有一个 Class 实例
  • 一个 Class 对象对应的是一个加载到JVM中的一个 .class 文件
  • 每个类的实例都会记得自己是由哪个 Class 实例所生成
  • 通过 Class 可以完整的得到一个类中的所有被加载的结构
  • Class 类是 Reflection 的根源, 针对任何你想动态加载 \ 运行的类, 唯有获得相应的 Class 对象

所有类都有 Class 对象, void/enum 也有

只要类型和维度一样, 不管对象里面的值一不一样, Class对象都是同一个对象

内存分析:

类加载过程:

类的加载过程.drawio

  • 加载: 将class文件字节码内容加载在内存中, 并将这些静态数据转换为方法区的运行时数据结构, 然后生成一个代表这个类的java.lang.Class对象
  • 链接: 将Java类的二进制代码合并到JVM的运行状态之中的过程
    • 验证: 确保加载的类信息符合JVM规范, 没有安全方面的问题
    • 准备: 正式为类变量(static) 分配内存并设置类变量默认初始值的阶段, 这些内存都将在方法区中进行分配
    • 解析: 虚拟机常量池内的符合引用(常量名)替换为直接引用(地址)的过程
  • 初始化:
    • 执行类构造器 <clinit>() 方法的过程. 类构造器 <clinit>() 方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句产生的. (类构造器是构造类)

获取字节码对象:

通过类名称获取:

className.class

class Test {
    void test() {
        try {
            Class<User> userClass = User.class;
            User user = userClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

通过对象获取:

object.class

class Test {
    void test() {
        User user = new User();
        Class<User> userClass = (Class<User>) user.getClass();
    }
}

通过 Class 的方法获取:

Class.forName(类路径)

class Test {
    void test() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> userClass = Class.forName("practise.User");
        Constructor<?> constructor = userClass.getDeclaredConstructor();
        User user = (User) constructor.newInstance();
    }
}

字节码对象的方法:

Declared 可以拿到所有不被权限限制

没有 Declared 只可以拿到可以访问的(包括父类)

获取构造器:

方法描述
Constructor<?>[] getConstructors()以列表的形式返回所有的可以访问的(包括父类)构造函数
Constructor<?>[] getDeclaredConstructors()以列表的形式返回所有的自身声明的构造函数, 不受权限限制
Constructor<T> getConstructor(Class<?>... parameterTypes)获取特定的可以访问的(包括父类)构造函数, 参数为对应构造函数的参数的字节码
Constructor<T> getDeclaredConstructor()获得自身声明的构造器, 不受权限限制

获取方法:

方法描述
Method getMethod(String name, Class<?>... parameterTypes)获取指定方法
Method[] getMethods() throws SecurityException获取所有可以访问的(包括父类)方法
Method getDeclaredMethod()
Method[] getDeclaredMethods()

获取属性:

方法描述
Field getField(String name) throws NoSuchFieldException, SecurityException获取属性, 形参为属性名
Field[] getFields() throws SecurityException获取属性
Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException获取属性
Field[] getDeclaredFields() throws SecurityException获取属性

Constructor 方法:

方法描述
String getName()获取构造函数名
native int getModifiers()获取构造函数访问修饰符. public-0x00000001, private-0x00000002, protected-0x00000004, static-0x00000008, final-0x00000010, synchronized-0x00000020, volatile-0x00000040, transient-0x00000080, native-0x00000100, interface-0x00000200, abstract-0x00000400
int getParameterCount()获取构造函数参数个数
Class<?>[] getParameterTypes()获取构造函数参数类型

Method 方法:

方法描述
void setAccessible(boolean flag)true 打破封装
Object invoke(Object obj, Object... args)执行方法, obj 是要执行此方法的类, args 是参数数组

通过反射实现对象Clone:

class TestReflect {

    @Test
    void test01() {
        User user = new User("Erzbir, 18, 1");
        User user1 = new User();
        copyObject(user, user1);
        System.out.println(user1);
    }

    // 通过反射复制(拷贝)对象
    private void copyObject(User user, User user1) {
        try {
            Class<?> clazz = user.getClass();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                Object o = field.get(user);
                field.set(user1, o);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}