7

这周在干什么

主要是在复习、看看老师写的代码、看看worktile、对知识进行扩充,还有不到两周就结课了,但是没有收到考试时间以及放假时间的通知......😅😅😅

什么是反射?

反射(Reflection)是 Java 的一种特性,它可以让程序在运行时获取自身的信息,并且动态地操作类或对象的属性、方法和构造器等。通过反射功能,可以让我们在不知道具体类名的情况下,依然能够实例化对象,调用方法以及设置属性。

反射机制关键在于Class对象,即类对象。是由Java虚拟机在JVM在加载类时自动创建的。

虚拟机创建类的过程:
当我们编写一个类并完成编译后,编译器会将转换为字节码存储在.class后缀文件中,在类 的加载过程中虚拟机利用Class Loader读取.class文件,将其中的字节码加载到内存中,并基于这些信息创建相应的Class对象,由于每个类值JVM中只加载一次所以每个类都对应着一个唯一的Class对象
image.png

demo

public class User extends People {
    public String name;
    private int age;

    private static int staticFiled = 10;
    private final String sex;
    protected String protectedFiled;

    static  {
        System.out.println("静态方法被执行");
    }

    public User(String name,String sex) {
        this.name = name;
        this.sex = sex;
    }

    private void privateMethod() {
        System.out.println("我是私有方法");
    }
    public void publicMethod() {
        System.out.println("我是公有方法");
    }

}

public class People {
    public String publicFiled;
    private String privateFiled;
}

获取Class对象的三种方式

第一种方式

通过类名 .class 进行获取,是在编译型进行获取的,所以可以明确指定类型是User不报错。使用这种方式对象经行获取时,不会出发类的初始化,只有在访问类的静态成员,或者实例的时候才会进行触发。

Class<User> userClass = User.class;

实例化对象

User userInstance = userClass.getDeclaredConstructor(String.class, String.class).newInstance("张三", "男");

第二种方式

通过对象的 getClass() 方法进行获取到。这种方法适用于某个类的已被实例化进行获取。
可以看到类型不是User,而是通配符 “”,原因:这个Class对象是从User实力获取的,而实例的具体类型只能在运行是进行创建和确定,而此时当前是编译阶段,因此无法获取到Class的类型

    User user = new User("张三", "男");
    Class<?> userClass = user.getClass();

实例化对象:

Constructor<?> constructor = userClass.getConstructor(String.class, String.class);

第三种方式

使用Class的forName()静态方法,通过完整路径获取,由于它也是运行才能知道类型的,所以类型是通配符,通过这种方式获取到Class对象会立即出发类的初始化。

Class<?> userClass = Class.forName("org.example.reflect.entity.User");

创建实例对象

Constructor<?> constructor = userClass.getDeclaredConstructor(String.class, String.class);
        constructor.newInstance("张三", "男");

获取到对象的属性

getFields() 获取所有公共字段,包括从父类继承的公共字段。

        Field[] fields = user.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }

输出:
public java.lang.String org.example.relect.entity.User.name
public java.lang.String org.example.relect.entity.People.publicFiled

getDeclaredFields()获取类中声明的所有字段,不管是什么级别,都可以获取到,但不包括从超类继承的字段,

Field[] fields = user.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field);
        }
//输出:
public java.lang.String org.example.relect.entity.User.name
private int org.example.relect.entity.User.age
private final java.lang.String org.example.relect.entity.User.sex
protected java.lang.String org.example.relect.entity.User.protectedFiled

如果想获取到父类的字段呢?getSuperclass()可以解决

Field[] fields = user.getSuperclass().getDeclaredFields();
//和之间的for循环一样,输出:
public java.lang.String org.example.relect.entity.People.publicFiled
private java.lang.String org.example.relect.entity.People.privateFiled

getField(String name)获取指定名称的公共字段

getDeclaredField(String name):获取指定名称的字段,无论其访问级别如何

尝试获取一个不存在的属性会出现什么问题呢?

可以从图下看出:在编译中,并没有报错,但是在执行的时候确报错了。
原因:通过反射获取字段的过程是在运行时动态执行的,所以在编译的阶段,无法进行错误的检测和捕获。
image.png

给属性设置值

我们在User实体中加入一个私有静态属性 private static final int staticFiled = 10;
尝试获取到这个值,此时出现了IllegalAccessException异常。
image.png

此时需要field.setAccessible(true),设置访问权限即可,
image.png

如果属性还使用了final关键字进行修饰。那么是否可以修改呢?

从下面的试验可以看出,即使是使用final关键字进行修改过的属性,也是可以修改的。

Class<?> userClass = Class.forName("org.example.reflect.entity.User");
        Constructor<?> constructor = userClass.getDeclaredConstructor(String.class, String.class);
        Object obj = constructor.newInstance("张三", "男");
        Field field = userClass.getDeclaredField("sex");
        field.setAccessible(true);
        field.set(obj, "女生");
        System.out.println(field.get(obj));

输出:
女生

获取方法

获取方法其实和获取属性是一样的,就不一一展示出来了。

  • getMethods()获取类及其父类中所有公共方法,包括继承的公共方法
  • getDeclaredMethods()获取类中声明的所有方法,包括公共、保护、和私有方法,但不包括继承的方法
  • getMethod(String name, Class<?>... parameterTypes)获取类中的特定公共方法,需指定方法名称和参数类型
  • getDeclaredMethod(String name, Class<?>... parameterTypes)获取类中声明的特定方法,无论其访问级别如何,需指定方法名称和参数类型。

总结

  • 从上面可以看出带Declare 的就是表示获取所有的字段、属性。不带Declare,就是获取所有公共(public)的属性、方法。
    例:
    getDeclaredField 表示获取所有的字段,getField(),所有的公共字段。
  • 反射可以绕过访问控制检查,不管是用private,还是final关键字进行修饰,都可以获取到,以及进行修改,这也破坏了封装性,所以在使用的时候需要格外谨慎使用。

参考资料

https://segmentfault.com/u/zhaokaiqiang
https://space.bilibili.com/19260126?spm_id_from=333.788.0.0


zZ_jie
401 声望9 粉丝

虚心接受问题,砥砺前行。