Java 反射机制

Posted Lucas小毛驴博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 反射机制相关的知识,希望对你有一定的参考价值。

一.Java反射机制介绍

? 反射机制主要在于这个字.

? 一般情况下,我们使用类的时候都是知道它是个什么类,比如定义了一个Person类,在使用的时候我们已经知道这个Person类是用来做什么的,于是我们就可以直接new一个Person类的实例对象,然后对这个实例对象进行操作,这是常规的操作。

public class Person {
    public void say() {
        System.out.println("hello java");
    }
    public static void main(String[] args) {
        Person p = new Person();
        p.say();
    }
}

? 而反射,就是与常规情况不同,在一开始我们并不知道要初始化的类对象是什么,导致我们没法使用new去实例化,但可以使用JDK提供的反射API进行调用,比如如下代码:(相关API下面会提及)

public class Person {
    public void say() {
        System.out.println("hello java");
    }
}

public class ReflectTest {
    public static void main(String[] args) throws Exception{
        // 获取class类对象
        Class<?> cls = Class.forName("Person");
        // 获取方法
        Method method = cls.getMethod("say");
        // 获取构造器
        Constructor<?> constructor = cls.getConstructor();
        // 创建对象
        Object obj = constructor.newInstance();
        // 执行对象的方法
        method.invoke(obj); // 输出 hello java
    }
}

? 以上两种方式效果都是一样的,区别就在第一段代码在还没运行时候就已经确定了要运行的类是Person,而第二段代码是在运行时候,由字符串传递了类名,才知道要执行的类是哪个。

? 所以,Java的反射机制就是指在运行中,才会知道要运行的类是哪一个,而且可以在运行时候获取这个类的所有属性和方法。

二.Java反射机制相关知识

  1. 理解Class类

    • 首先得清楚,在java中,除了int等基础类型外,其他类型全都是class.

    • class是由JVM在执行过程中动态加载的,每加载一种class,JVM就会为其创建一个Class类型的实例,并关联起来,这里的Class类型,也是class的一种,只是名字和class相似(Java是大小写敏感的)。

    • 第二,Class类的实例内容就是创建的类的类型信息,包括类名、包名、父类、实现的接口、所有方法、所有成员变量等。

      比如我们创建了一个Person类,那么java就会生成一个内容是Person的Class类实例这也意味着如果我们获取了某个类对象的Class实例,我们就可以知道这个Class实例所对应的class的所有信息(这就是反射)

    • 第三,Class类的对象不像普通类一样,是不可以使用new来创建的,它只能由JVM创建,因为这个类并没有public的构造方法。

  2. 如何获取Class类对象

    有三种方式可以获取Class类对象

    • 类名.class:通过类名的属性class获取

      比如:Class cls = String.class;

    • Class.forName():通过Class类的静态方法forName获取

      用于已知一个类的完整类名(包括包名)

      Class cls = Class.forName("java.lang.String");

    • 对象.getClass():通过实例对象的getclass方法获取

      用于已知实例对象的情况。

      String str = "hello";
      Class cls = str.getClass();

    注意:因为Class类实例在JVM中是唯一的,所以上面三种方法获取的Class实例都是同一个的。

  3. JVM动态加载

    ? JVM在执行java程序时候,并不是一次性把所有的class全部加载到内存的,而是在第一次需要用到class时候才会加载,比如下面的例子,执行ReflectTest.java时,用到了main,所以JVM会先把ReflectTest.class加载到内存,但不会加载Person.class,当执行到了new Person()时候,发现需要用到Person类时候,才会去加载Person.class,这就是JVM动态加载的特性。

    public class Person {
        public void say() {
            System.out.println("hello java");
        }
    }
    
    public class ReflectTest {
        public static void main(String[] args) {
            Person p = new Person();
            p.say();
        }
    }

    ? 正是有JVM动态加载的特性,我们才可以在运行时根据条件来加载不同的类

三.Java反射机制的使用

? 现在我们已经知道了怎么获取Class实例了,也知道了我们可以获取class的所有信息,但我们一般是通过这个Class实例来获取成员变量Field、构造器Constructor以及成员方法Method

? 在Class类实例中,是将类的各个组成部分(成员变量,成员方法,构造器等)封装为其他对象的,所以我们通过Class实例获取到也是一个个对象,每个对象内有各自的方法。

? 下面就看看如何获取这些信息:

  1. 获取成员变量Field

    • Field[] getFields():获取所有public修饰的成员变量
    • Field getField(String name):获取指定名称的public修饰的成员变量
    • Field getDeclaredFields():获取所有的成员变量,不考虑修饰符
    • Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符

    上面说过,Class类实例中式将类的每一部分都封装为对象,那么每一个成员变量,从Class类实例中获取到的也是一个对象Field,要操作成员变量,就是操作这个Field对象的成员,其主要方法有以下几个:

    • void set(Object obj, Object value):设置值
    • Object get(Object obj):获取值
    • void setAccessible(boolean flag):设置允许访问
    public class Person {
        public int age;
        private String name;
    
        public void say() {
            System.out.println("hello java");
        }
    }
    import java.lang.reflect.*;
    
    public class ReflectTest {
        public static void main(String[] args) throws Exception{
            Class<?> cls = Class.forName("Person");
            Constructor<?> constructor = cls.getConstructor();
            Object obj = constructor.newInstance();
    
            // 获取所有public的成员变量
            Field[] fields = cls.getFields();
            for (Field f : fields) {
                System.out.println(f.getName());
            }    // 输出:age
            // 获取指定名字的public成员变量
            Field field = cls.getField("age");
            field.set(obj, 20);
            System.out.println(field.get(obj));// 输出:20
            // 获取所有的成员变量,不考虑修饰符
            Field[] fields1 = cls.getDeclaredFields();
            for(Field f : fields1) {
                System.out.println(f.getName());
            }// 输出: age name
            // 获取指定名字的成员变量,不考虑修饰符,这里操作私有变量name
            Field f = cls.getDeclaredField("name");
            f.setAccessible(true);
            f.set(obj, "hello");
            System.out.println(f.get(obj));  // 输出:hello
        }
    }

    注意:

    ? 在获取private修饰的成员变量时,如果没有加上这一句f.setAccessible(true);是会报一个异常java.lang.IllegalAccessException: Class ReflectTest can not access a member of class Person with modifiers "private",是因为正常情况下,是无法访问Person类的private字段的,而setAccessible(true)的意思是不管字段是不是public,一律运行访问

  2. 获取构造器Constructor

    • Constructor<?>[] getConstructors():获取所有public的构造器
    • Constructor<T> getConstructor(Class<?>... parameterTypes):获取指定参数的public的构造器
    • Constructor<?>[] getDeclaredConstructors():获取所有的构造器
    • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):获取指定参数的构造器,不考虑修饰符

    注意:这里的参数Class<?>... parameterTypes是指可变参数,可以有一个,也可以没有,类型是Class类型的,比如参数为int,则为int.class,就是上述说的三种获取Class类对象的方法。

    同样,获取构造器返回的是Constructor对象,也有几个常用操作的方法

    • T newInstance(Object... initargs)::创建一个实例
    • void setAccessible(boolean flag):设置允许访问
    public class Person {
        public int age;
        private String name;
    
        public Person() {
            System.out.println("public无参构造方法");
        }
    
        public Person(int age,String name) {
            System.out.println("age = " + age + ",name = " + name);
        }
    }
    import java.lang.reflect.*;
    
    public class ReflectTest {
        public static void main(String[] args) throws Exception{
            Class<?> cls = Class.forName("Person");
            // 获取所有public修饰的构造方法
            Constructor<?>[] constructors = cls.getConstructors();
    
            // 获取无参构造方法
            // Constructor<?> c0 = cls.getConstructor();
            // c0.newInstance();
            cls.newInstance(); // 对于空参的构造方法生成对象的,可以直接通过Class对象的newInstance()生成
    
            // 获取指定参数列表的构造方法
            Constructor<?> c = cls.getConstructor(int.class, String.class);
            c.newInstance(10, "Apple");
        }
    }

    如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法。

  3. 获取成员方法Method

    • Method[] getMethods():获取所有public的方法(包括父类)
    • Method getMethod(String name, Class<?>... parameterTypes):获取public的方法(包括父类)
    • Method[] getDeclaredMethods():获取当前类的所有方法(不包括父类)
    • Method getDeclaredMethod(String name, Class<?>... parameterTypes):获取当前类的某个方法(不包括父类)

    获取方法相对应返回的对象是Method,主要有以下几个方法调用:

    • Object invoke(Object obj, Object... args):执行方法
    • String getName():获取方法名
    • void setAccessible(boolean flag):设置允许访问

    父类Person:

    public class Person {
        public int age;
        private String name;
    
        public Person(int age, String name) {
            this.age = age;
            this.name = name;
        }
    
        public String getName() {
            return this.name;
        }
    }

    子类Student:

    public class Student extends Person {
        private int score;
    
    
        public Student(int age, String name, int score) {
            super(age, name);
            this.score = score;
        }
    
        public static void staticMethod(){
            System.out.println("static Method");
        }
    
        public int getScore() {
            return score;
        }
    
        public void setScore(int score) {
            this.score = score;
        }
    
        private int getAge() {
            return this.age;
        }
    }

    反射操作:

    import java.lang.reflect.*;
    
    public class ReflectTest {
        public static void main(String[] args) throws Exception{
            Class<?> cls = Student.class;
    
            // 获取构造器
            Constructor<?> con = cls.getConstructor(int.class, String.class);
            // 生成实例
            Object obj = con.newInstance(20, "zhangsan",90);
            // 获取类的所有public方法(包括父类方法)
            Method[] methods = cls.getMethods();
            for (Method m : methods) {
                System.out.print(m.getName() + " ");
            } // 输出:getScore setScore staticMethod getName wait wait wait equals toString hashCode getClass notify notifyAll
    
            // 获取子类某个public方法 
            Method getScoreMethod = cls.getMethod("getScore");
            System.out.println(getScoreMethod.invoke(obj)); // 输出: 90
    
            // 获取父类某个public方法
            Method getNameMethod = cls.getMethod("getName");
            System.out.println(getNameMethod.invoke(obj));  // 输出:zhangsan
    
            // 获取当前类的所有方法
            Method[] methods2 = cls.getDeclaredMethods();
            for (Method m : methods2) {
                System.out.print(m.getName() + " ");
            } // 输出:getAge setScore staticMethod getScore
    
            // 获取当前类的指定的方法 私有方法--getAge
            Method getAgeMethod = cls.getDeclaredMethod("getAge");
            getAgeMethod.setAccessible(true);
            System.out.println(getAgeMethod.invoke(obj)); // 输出: 20
            // 获取静态方法
            Method getStaticMethod = cls.getDeclaredMethod("staticMethod");
            getStaticMethod.invoke(null); // 输出: static Method
        }
    }

    注意:如果调用静态方法,是不需要指定实例对象的,所以invoke方法传入的第一个参数设置为null

四.总结

? 从以上的几个例子可看出,一般我们使用反射获取一个对象的步骤有以下几步:

  • 获取类的Class对象实例(有三种方式)

    比如:Class cls = Student.class;

  • 使用Class对象实例获取构造方法对象

    比如:Constructor constructor = cls.getConstructor();

  • 使用构造器方法对象的newInstance()方法创建对象

    比如:Object obj = constructor.newInstance();

  • 进行相关操作,获取成员变量,获取方法,调用方法等。

以上是关于Java 反射机制的主要内容,如果未能解决你的问题,请参考以下文章

反射机制入门

反射机制入门

java 反射代码片段

深入理解java的反射机制

Java反射机制

Java核心技术梳理-类加载机制与反射