Java反射入门

1.概述

反射就是在运行状态,对于任意一个类,都可以直达这个类的属性和方法;对于任何一个对象,都可以调用它的任意一个方法和属性。而对于官方的解释是,反射允许对封装类的字段、方法以及构造函数进行编程访问。

2.反射的使用方式

反射的应用场景主要是先获取class对象,然后根据需求获取构造函数、成员变量和成员方法的一个或多个,并对其进行相应的操作。

2.1获取class对象的三种方式

①Class.forName("类的路径“): 当知道该类的全路径名时,可以使用该方法获取 Class 类对象。

②类名.class。只适合在编译前就知道操作的 Class,通常在作为参数的场景中使用。

③对象名.getClass()。只适合已有这个对象时使用。

2.2获取构造函数

 1)提供的方法

方法 说明

Constructor<?>[] getConstructors()

获取对象的所有公共构造方法
Constructor<?>[] getDeclaredConstructors() 获取对象的所有构造方法
Constructor<T> getConstructor(Class<?>... parameterTypes) 根据参数类型 获取对象的单个公共构造方法
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 根据参数类型 获取对象的单个构造方法
T newInstance(Object ... initargs) 根据指定的构造方法创建对象

2)方法实战

首先创建一个对象,添加构造方法,使用不同的权限修饰符

package com.zxh.demo.entity;

public class User {
    public String name;
    private String pwd;
    private Integer age;

    public User() {
    }

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

    private User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public User(String name, String pwd, Integer age) {
        this.name = name;
        this.pwd = pwd;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                ", age=" + age +
                '}';
    }
}

获取构造方法

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //利用反射获取类
        Class<?> aClass = Class.forName("com.zxh.demo.entity.User");
        //获取所有公共的构造方法
        Constructor<?>[] constructors = aClass.getConstructors();
        System.out.println("User类所有公共的构造方法");
        Arrays.stream(constructors).forEach(System.out::println);
        //获取所有的构造方法
        Constructor<?>[] constructors2 = aClass.getDeclaredConstructors();
        System.out.println("User类所有的构造方法");
        Arrays.stream(constructors2).forEach(System.out::println);
        //根据参数获取构造方法
        Constructor<?> con1 = aClass.getConstructor(String.class);
        System.out.println("根据参数获取构造方法1:" + con1);
        Constructor<?> con2 = aClass.getDeclaredConstructor(String.class, Integer.class);
        System.out.println("根据参数获取构造方法2:" + con2);
        Constructor<?> con3 = aClass.getConstructor(String.class, Integer.class);
        System.out.println("根据参数获取构造方法3:" + con3);
    }

在上述代码中,其实就用到了获取class对象的两种方式。在根据参数获取构造方法时,传入了参数类型,这个参数需要和构造方法中的参数保持一致。打印结果如下

可用看出每个构造方法都标识了权限修饰符和参数类型。getDeclaredConstructors()和getConstructors()的区别在于,前者可获取对象的所有构造方法,而后者只能获取到公共的,通过权限修饰符可用明显的看出来。同理,根据参数获取构造方法也是这样区分的。另外,在根据参数调用公共获取构造方法的方法时抛出了异常,说是没有找到这个构造方法,原因是这个方法是私有的,使用公有的方法自然获取不到。

除此之外,还有很多的方法,这里就列举几个进行说明:

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //利用反射获取类
        Class<?> aClass = Class.forName("com.zxh.demo.entity.User");
        Constructor<?> con2 = aClass.getDeclaredConstructor(String.class, Integer.class);
        System.out.println("根据参数获取构造方法2:" + con2);
        int modifiers = con2.getModifiers();
        System.out.println("构造方法权限修饰符:" + modifiers);//返回是的数字,在jdk api文档中有对此常量的说明
        Parameter[] parameters = con2.getParameters();
        System.out.println("获取构造方法所有的参数信息");
        Arrays.stream(parameters).forEach(System.out::println);
        //暴力反射:临时取消权限校验
        con2.setAccessible(true);
        //根据构造方法创建对象
        Object newInstance = con2.newInstance("张三", 23);
        System.out.println(newInstance);
    }

在创建对象时,由于此时获取的构造方法是私有的,也就是说正常情况下是不能使用此构造方法创建对象。虽然通过反射的机制获取到了此构造函数,但无法使用此方法。那么此时就需要使用setAccessilbe()设置值为true来临时取消权限访问校验,对于成员方法也可以这样处理。打印结果如下

在创建对象时,idea的提示就用到了反射(后面包括方法和属性的调用),只能使用公有的构造方法创建对象

2.3获取成员变量

 获取成员变量(属性)和上述获取构造方法的方式是类似的,其方法如下

方法 说明

Field[] getFields()

获取对象的所有公共属性
Field[] getDeclaredFields() 获取对象的所有属性
Field getField(String name) 根据属性名称 获取对象的单个属性
Field getDeclaredField(String name) 根据属性名称 获取对象的单个属性
void set(Object obj, Object value) 给对象的属性设置值
Object get(Object obj) 获取属性的值

首先需要给对象添加get和set方法(设置不同的权限修饰符)

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public Integer getAge() {
        return age;
    }

    private void setAge(Integer age) {
        this.age = age;
    }

获取属性的基本信息

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        //利用反射获取类
        Class<?> aClass = Class.forName("com.zxh.demo.entity.User");

        Field[] fields = aClass.getFields();
        System.out.println("获取所有公共的属性");
        Arrays.stream(fields).forEach(System.out::println);
        Field[] fields2 = aClass.getDeclaredFields();
        System.out.println("获取所有的属性");
        Arrays.stream(fields2).forEach(System.out::println);
        Field field = aClass.getField("name");
        System.out.println("根据属性名获取属性1:" + field);
        Field field2 = aClass.getDeclaredField("age");
        System.out.println("根据属性名获取属性2:" + field2);
        Field field3 = aClass.getField("age");
        System.out.println("根据属性名获取属性3:" + field3);
    }

打印结果

 可以看出,属性都包含权限修饰符、属性类型和属性名称。当使用公有的方式去获取私有的属性时也会抛异常。

除此之外,还可以给属性设置值和获取值:

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        //利用反射获取类
        Class<?> aClass = Class.forName("com.zxh.demo.entity.User");
        Field field2 = aClass.getDeclaredField("age");
        System.out.println("根据属性名获取属性2:" + field2);
        field2.setAccessible(true);//私有,必须设置为true
        User user = new User();
        field2.set(user, 20);
        Object o = field2.get(user);
        System.out.println(o);
        System.out.println(user);

    }

打印结果如下

可以看到,上述是给用户对象的age设置值,也可以来获取对象的这个属性,前提是把对象作为参数进行传递。

2.4获取成员方法

获取成员方法和上述获取成员方法的方式是类似的,其方法如下

方法 说明

 Method[] getMethods()

获取对象的所有公共方法。会一并获取到父类的公有方法
Method[] getDeclaredMethods() 获取对象的所有方法,不会获取父类的方法
Method getMethod(String name, Class<?>... parameterTypes) 根据方法名和形参类型 获取单个方法
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 根据方法名和形参类型 获取单个方法
Object invoke(Object obj, Object... args) 运行方法。参数一:调用给方法的对象。参数二:调用该方法的参数,没有就不写

首先需要给对象添加两个方法用于演示(设置不同的权限修饰符)

    public void say(String title) {
        System.out.println("我在讲话,主题是:" + title);
    }

    protected void running(String addr) {
        System.out.println("我正在" + addr + "跑步");
    }

获取方法并执行

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //利用反射获取类
        Class<?> aClass = Class.forName("com.zxh.demo.entity.User");

        Method[] methods = aClass.getMethods();
        System.out.println("获取所有公共的方法");
        Arrays.stream(methods).forEach(System.out::println);
        Method[] methods2 = aClass.getDeclaredMethods();
        System.out.println("获取所有的方法");
        Arrays.stream(methods2).forEach(System.out::println);
        Method method = aClass.getMethod("say", String.class);
        System.out.println("获取方法1:" + method);
        User user = new User();
        method.invoke(user, "学习反射");
        Method method2 = aClass.getDeclaredMethod("running", String.class);
        System.out.println("获取方法2:" + method2);
        method2.setAccessible(true);
        method2.invoke(user, "公园");
        Method method3 = aClass.getMethod("running", String.class);
        System.out.println("获取方法3:" + method3);
    }

执行结果,方法过多,只截取部分

获取单个方法时,不仅要指定方法名还要传入形参类型,为的是解决方法重载问题。

通过上述几个小节的介绍,那么在使用反射时,无论是获取成员变量还是成员方法,通常都会使用获取所有的方式,并不会只获取公有的,其优势很明显。

2.5实战演练

1)获取对象的所有属性名和值

2)根据传入的参数动态创建对象并调用方法

以上两个都是简单的用法,实际应用场景会复杂的多。

posted @ 2023-03-01 11:10  钟小嘿  阅读(86)  评论(0编辑  收藏  举报