Java反射机制

516 阅读5分钟
  • 什么是反射?

Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。

用大白话来说就是反射在运行状态时可以操作任意一个类的全部功能,比如调用方法、修改成员变量的值和调用构造方法等,就算是私有也可以暴力反射,sorry~反射就是可以为所欲为

  • 获取字节码Class

要想反射一个类,必须先拿到字节码Class才可以,获取字节码的方法有三种

       //第一种方法
       Student student = new Student();
       Class<? extends Student> studentClass1 = student.getClass();

       //第二种方法
       Class<Student> studentClass2 = Student.class;
       
       //第三种方法
       try {
           Class<?> studentClass3 = Class.forName("senior.reflection.Student");
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       }
  • 获取类的成员变量

public class Student {
    private int age = 18;
    private String name = "张三";
    public String gender = "男";
}

在这里插入图片描述

  1. getFields(): 只能获取到public修饰的成员变量
  2. getField(String name): 通过变量名获取成员变量,只能是被public修饰的
  3. getDeclaredFields(): 可以获取到所有成员变量
  4. getDeclaredField(String name): 通过变量名获取成员变量,任何修饰符都可以
        Student student = new Student();
        Class<? extends Student> studentClass = student.getClass();

        //getFields()
        for (Field field : studentClass.getFields()) {
            System.out.println("getFields(): " + field.getName());
        }

        System.out.println("-----------------");

        //getDeclaredFields(**)
        for (Field field : studentClass.getDeclaredFields()) {
            System.out.println("getDeclaredFields(): " + field.getName());
        }

        System.out.println("-----------------");

        //getDeclaredField(String name)
        Field age1 = studentClass.getDeclaredField("age");
        System.out.println("getDeclaredField(String name): "+age1.getName());

        System.out.println("-----------------");

        //getField(String name)
        Field age2 = studentClass.getField("age");
        System.out.println("getField(String name): " + age2.getName());

运行结果: 在这里插入图片描述

  • 修改和获取成员变量的值

public class Student {
    private int age = 18;
    private String name = "张三";
    public String gender = "男";
}

我们以私有成员变量 private int age = 18 为例:

        Student student = new Student();
        Class<? extends Student> studentClass = student.getClass();

        Field age = studentClass.getDeclaredField("age");

        //修改前的age
        int beforeAge = age.getInt(student);
        System.out.println("修改前的age: "+beforeAge);

        //修改age为38
        age.setInt(student, 38);

        //修改后的age
        int afterAge = age.getInt(student);
        System.out.println("修改后的age: "+afterAge);

运行结果: 在这里插入图片描述

不是说好的私有的也可以操作吗? 为什么会报错.... 不要慌,暴力反射来了!

age.setAccessible(true);

这行代码可以反射私有的成员变量、构造方法和普通方法等,本质上就是反射的时候不校验权限了,所以操作私有成员变量的时候也就不会报错了,这个我们称为“暴力反射”。

现在我们加上暴力反射再来一次:

        Student student = new Student();
        Class<? extends Student> studentClass = student.getClass();


        Field age = studentClass.getDeclaredField("age");
        
        //暴力反射
        age.setAccessible(true);

        //修改前的age
        int beforeAge = age.getInt(student);
        System.out.println("修改前的age: "+beforeAge);

        //修改age为38
        age.setInt(student, 38);

        //修改后的age
        int afterAge = age.getInt(student);
        System.out.println("修改后的age: "+afterAge);

运行结果: 在这里插入图片描述

  • 获取类的构造方法

在这里插入图片描述

  1. getConstructors(): 只能获取到public修饰的构造方法
  2. getConstructor(Class<?>... parameterTypes): 通过参数的字节码获取到public修饰的构造方法
  3. getDeclaredConstructors(): 可以获取到所有构造方法
  4. getDeclaredConstructor(Class<?>... parameterTypes): 通过参数的字节码获取到构造方法,任何修饰符都可以
	public class Student {
	    public Student() {
	        System.out.println("空参构造方法被调用了");
	    }
	
	    //私有方法
	    private Student(int age, String name) {
	        System.out.println("多参私有构造方法被调用了: " + age + "   " + name);
	    }
   }

        //使用类名拿到字节码Class
        Class<?> studentClass = Class.forName("senior.reflection.Student");

        //getConstructors()
        for (Constructor<?> constructor : studentClass.getConstructors()) {
            System.out.println("getConstructors(): "+constructor);
        }

        //getConstructor()
        System.out.println("--------------------------------------");
        Constructor<?> constructor = studentClass.getConstructor();
        Student student = (Student) constructor.newInstance();
        System.out.println("getConstructor(): "+constructor);

        //getDeclaredConstructors()
        System.out.println("--------------------------------------");
        for (Constructor<?> declaredConstructor : studentClass.getDeclaredConstructors()) {
            System.out.println("getDeclaredConstructors(): "+declaredConstructor);
        }

        //getDeclaredConstructor()
        System.out.println("--------------------------------------");
        Constructor<?> declaredConstructor = studentClass.getDeclaredConstructor(int.class, String.class);
        System.out.println("getDeclaredConstructor(): "+declaredConstructor);
        //因为是私有的构造方法,所以要用暴力反射
        declaredConstructor.setAccessible(true);
        //传入参数到构造方法创建对象
        Student student = (Student) declaredConstructor.newInstance(28, "李四");

运行结果: 在这里插入图片描述

  • 实例化对象

既然构造方法可以拿到了,那就可以通过构造方法实例化对象了

  • 通过空参构造方法实例化对象:

	public class Student {
	    public Student() {
	        System.out.println("空参构造方法被调用了");
	    }
   	}



	//使用类名拿到字节码Class
   Class<?> studentClass = Class.forName("senior.reflection.Student");
     
   Constructor<?> constructor = studentClass.getConstructor();
   Student student = (Student) constructor.newInstance();
        
  • 通过多参构造方法实例化对象:

	public class Student {
	    //私有方法
	    private Student(int age, String name) {
	        System.out.println("多参私有构造方法被调用了: " + age + "   " + name);
	    }
   	}



    //使用类名拿到字节码Class
    Class<?> studentClass = Class.forName("senior.reflection.Student");
    
    Constructor<?> declaredConstructor = studentClass.getDeclaredConstructor(int.class, String.class);
    //因为是私有的构造方法,所以要用暴力反射
    declaredConstructor.setAccessible(true);
    //传入参数到构造方法创建对象
    Student student = (Student) declaredConstructor.newInstance(28, "李四");
  • 获取和调用类的成员方法

在这里插入图片描述

  1. getMethods(): 只能获取到public修饰的成员方法
  2. getMethod(String name, Class[] parameterTypes): 获取到public修饰的成员方法,第一个参数: 方法名, 第二个参数:参数类型的class字节码
  3. getDeclaredMethods(): 可以获取到所有成员方法
  4. getDeclaredMethod(String name, Class[] parameterTypes): 获取到所有匹配的成员方法,第一个参数: 方法名, 第二个参数:参数类型的class字节码

如下代码: setAge()和getAge()为公开方法 setNameAndGender()和getNameAndGender()为私有方法

	public class Student {
	    private int age = 18;
	    private String name = "张三";
	    public String gender = "男";
	
	    public int getAge() {
	        return age;
	    }
	
	    public void setAge(int age) {
	        this.age = age;
	    }
	
	    /**
	     * 私有方法
	     */
	    private String getNameAndGender() {
	        return name;
	    }
	
	    /**
	     * 私有方法
	     */
	    private void setNameAndGender(String name,String gender) {
	        this.name = name;
	        this.gender = gender;
	    }
	}

反射获取和调用方法:


      //使用类名拿到字节码Class
      Class<?> studentClass = Class.forName("senior.reflection.Student");
      Object student = studentClass.newInstance();

      //getMethods()
      for (Method method : studentClass.getMethods()) {
          System.out.println("getMethods(): "+method.getName());
      }

      //getDeclaredMethods()
      System.out.println("-------------------------------------");
      for (Method method : studentClass.getDeclaredMethods()) {
          System.out.println("getDeclaredMethods(): "+method.getName());
      }

      //getMethod()
      System.out.println("-------------------------------------");
      Method setAge = studentClass.getMethod("setAge", int.class);
      //通过invoke可以操作方法 参数1: 要操作的对象 参数2: 要传入的参数
      setAge.invoke(student,48);

      Method getAge = studentClass.getMethod("getAge");
      //invoke的返回值就是方法的返回值
      Object age = getAge.invoke(student);
      System.out.println("getMethod() 获取到年龄: "+age);

      //getMethod()
      System.out.println("-------------------------------------");
      Method setNameAndGender = studentClass.getDeclaredMethod("setNameAndGender", String.class, String.class);
      //暴力反射
      setNameAndGender.setAccessible(true);
      //通过invoke可以操作方法 参数1: 要操作的对象 参数2: 要传入的参数
      setNameAndGender.invoke(student,"王五","女");

运行结果: 在这里插入图片描述

  • 总结

  1. getDeclaredXxx()的方法可以获取到所有信息,但是不包含继承的父类的信息
  2. getXxx()的方法只能获取到public修饰的信息,但是也会包含继承的父类的public信息
  3. 被私有的信息可以通过暴力反射setAccessible(true)来操作
  4. 把对象实例化可以通过class.newInstance()
  • 实战 - 如何在ArrayList中存入字符串?

现在学完了反射这个问题应该很简单了

  1. 通过反射拿到list的add方法
  2. 通过invoke给add方法传参
  3. 打印list的数据
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //创建一个Integer类型的数组
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);

        //通过反射拿到list的add方法
        Method add = list.getClass().getMethod("add", Object.class);
        //使用invoke为方法传参
        add.invoke(list,"我是字符串");

        for (Object item : list) {
            System.out.println(item);
        }
    }

运行结果:在这里插入图片描述