Skip to content

反射

反射机制是后续框架的灵魂,可以通过外部配置文件,在不修改源码情况下,只修改配置文件中的内容,来控制程序,也符合设计模式的ocp原则(开闭原则:不修改源码,来扩容功能)

java
package com.test;

public class Cat {
    private String name = "招财猫";
    public void hi() {
        System.out.println("hi" + name);
    }
}
properties
# re.properties配置文件的内容为
classfullpath=com.test.Cat
method=hi
java
// 使用反射机制根据配置项内容来加载类,并调用配置项中的类方法
// 使用Properties类,读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\re.properties"));  // 加载配置文件夹
// 读取配置项内容
String classfullpath = properties.get("classfullpath").toString();
String methodName = properties.get("method").toString();

// 加载类,返回Class类型的对象
Class cls = Class.forName(classfullpath);
// 通过cls得到加载的类com.test.Cat的对象实例
Object o = cls.newInstance();  // o的运行类型是com.test.Cat
// 通过cls得到加载类com.test.Cat的methodName(hi)的方法对象(即在反射中,可以把方法视为对象)
Method method1 = cls.getMethod(methodName);
// 通过method1对象调用方法,即通过方法对象来实现调用方法
method1.invoke(o);   // 在反射机制中  方法.invoke(对象)

后续我们改变具体对象的方法时,我们只需要在配置文件中进行修改即可,不需要修改源码,这就是反射机制的厉害之处


基本概念

反射机制允许程序在执行期间借助于Reflection API取到任何类的内部信息(如成员变量,构造器,成员方法等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到

加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构,这个Class对象就像一面镜子,透过这个镜子可以看到类的结构,所以,形象的称之为反射

如通过Person类实例化的对象p,那么这个p的类型就是Person类;同理Class类实例化出的cls对象,其类型是Class

Java反射机制原理示意图:

image-20250428200533444

Java反射机制可以完成:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构建任意一个类的对象
  • 在运行时得到任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的成员变量和方法
  • 生成动态代理

反射相关的主要类:这些类在java.lang.reflection

java
package com.test;

public class Cat {
    private String name = "招财猫";
    public int age = 3;
    
    public Cat() {}   // 无参构造器
    public Cat(String name) {
        this.name = name;
    }
    
    public void hi() {
        System.out.println("hi" + name);
    }
}
  • java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象

    java
    Object o = cls.newInstance();  // o的运行类型是com.test.Cat
  • java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法

    java
    Method method1 = cls.getMethod(methodName);
    // 通过method1对象调用方法,即通过方法对象来实现调用方法
    method1.invoke(o);   // 在反射机制中  方法.invoke(对象)
  • java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量

    java
    Field ageField = cls.getField("age");  // getField()方法不能获得私有的属性,可以获得公有的
    System.out.println(ageField.get(o));   // 3  反射的形式:成员变量对象.get(对象)
  • java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示构造器

    java
     // getConstructor()获取构造器,()中可以指定构造器参数类型,没有东西返回无参构造器
    Constructor constructor = cls.getConstructor();
    System.out.println(constructor);   // public com.test.Cat()
    
    // 传入的String.class是String类的Class对象
    Constructor constructor2 = cls.getConstructor(String.class);  
    System.out.println(constructor2);   // public com.test.Cat(java.lang.String)

反射的优缺点:

  • 优点:可以动态的创建和使用对象(也就是框架底层核心),使用灵活,没有反射机制,框架技术就失去了底层支持

  • 缺点:使用反射基本是解释执行,对执行的速度有影响

    可以使用反射调用优化中的关闭访问检查方法:MethodFieldConstructor对象都有setAccessible(boolean)方法,这个方法的作用是启动和禁用访问安全检查的开关,参数true表示反射的对象在使用时取消访问检查,提高反射的效率,如:ageField.setAccessible(true);参数值为false则表示反射的对象执行访问检查

    该方法有提高速度,但是也不会提高太多


Class

Class类的基本介绍:

  • Class也是类,因此也继承Object

    image-20250428204405543

  • Class类对象不是new出来的,而是系统创建的(Class类对象是通过类加载器生成的,具体是通过ClassLoader中的loadClass()方法完成类加载的,从而生成某个类对应的Class对象)

    java
    // 底层源码
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    ClassLoader类,仍然是通过ClassLoader类加载具体类的Class对象

  • 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次

    java
    // 加载相同的类
    Class cls1 = Class.forName("com.test.Cat");
    Class cls2 = Class.forName("com.test.Cat");
    // cls1和cls2的哈希Code值是一样的,说明两者是同一个对象
  • 每个类的实例都会记得自己是由哪个Class实例所生成的

  • 通过Class对象可以完整的得到一个类的完整结构,通过一系列方法API去实现,Class类常用的方法有:

    image-20250428210554966

    演示Class类的常用方法:

    java
    package com.test;
    
    // 定义一个类
    public class Car {
        public String brand = "宝马";
        public int price = 500000;
        public String color = "白色";
        
        @Override
        public String toString() {
            return "Car{" + "brand='" + brand + '\'' + ", price=" + price + ", color" + '\'' + '}';
        }
    }
    java
    // 演示Class类的常用方法
    public class Class01 {
        public static void main(String[] args) throws Exception {
            String classAllPath = "com.test.Car";
            // 获取到Car类对应的Class对象
            Class<?> cls = Class.forName(classAllPath);  // <?>表示不确定的Java类型
            // 输出cls,显示cls对象,是哪个类的Class对象
            System.out.println(cls);  // class com.test.Car
            // 输出运行类型
            System.out.println(cls.getClass());  // class java.lang.Class
            // 得到包名
            System.out.println(cls.getPackage.getName());  // com.test  当前类在哪个包下
            // 得到全类名
            System.out.println(cls.getName);  // com.test.Car
            // 通过cls创建对象实例
            Object car = cls.newInstance();  // 也可以写为 Car car = (Car)cls.newInstance();
            System.out.println(car);  // 会直接调用car.toString()方法
            // 通过反射获取属性brand
            Field brand = cls.getField("brand");  // 获取对象中的私有属性会报错
            System.out.println(brand.get(car));  // 宝马
            // 通过反射给属性赋值
            brand.set(car, "奔驰");
            System.out.println(brand.get(car));  // 奔驰
            // 获取所有的属性(字段)
            Field[] fields = cls.getFields();
            for(Field f: fields) {
                System.out.println(f.getName());  // brand price color
            }
        }
    }
  • Class对象是存放在堆中的

  • 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名和访问权限等)

哪些类型有Class对象

Java中,如下的类型有Class对象:

  • 外部类,成员内部类,静态内部类,局部内部类,匿名内部类

    java
    Class<String> cls = String.class;  // class java.lang.String
  • interface接口

    java
    Class<Serializable> cls = Serializable.class;   // interface java.io.Serializable
  • 数组

    java
    Class<Integer[]> cls1 = Integer[].class;  // class [Ljava.lang.Integer
    Class<float[][]> cls1 = float[][].class;  // class [[F
  • enum枚举

    java
    Class<Thread.State> cls = Thread.State.class;  // class java.lang.Thread$State
  • annotation注解

    java
    Class<Deprecated> cls = Deprecated.class;   // interface java.lang.Deprecated
  • 基本数据类型

    java
    Class<Long> cls = Long.class;  // long
  • void

    java
    Class<Void> cls = void.class;  // void

获取Class类对象

在程序运行的不同阶段,我们可以根据不同的方式来获取Class对象

获取Class类对象有以下几种常用的方式:

  1. 已知一个类的全类名,且该类在类路径下,可以通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException,该方法使用在代码编译阶段

    应用场景:多用于配置文件,读取类全路径,加载类(在底层框架中使用的比较多)

    java
    // Class.forName()
    String classAllPath = "com.test.Car";   // 通过读取配置文件
    class<?> cls = Class.forName(classAllPath);
    System.out.println(cls);   // class com.test.Car
  2. 若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最好,该方法使用在加载阶段

    应用场景:多用于参数传递,比如通过反射得到对应构造器对象

    java
    // 具体的类名.class
    Class cls = Car.class;
    System.out.println(cls);  // class com.test.Car
  3. 已知某个类的实例(具体的对象已经存在了),调用该实例的getClass()方法获取Class对象(即查看对象的运行类型,即这个对象关联的Class类对象),该方法一般用于运行阶段

    应用场景:通过创建好的对象,获取Class对象

    java
    Car car = new Car();
    Class cls = car.getClass();
    System.out.println(cls);   // class com.test.Car
  4. 通过类加载器(有四种类加载器)来获取到类的Class对象

    java
    Car car = new Car();
    // 先得到类加载器
    ClassLoader classLoader = car.getClass().getClassLoader();
    // 通过类加载器得到Class对象
    String classAllPath = "com.test.Car";
    Class<?> cls = classLoader.loadClass(classAllPath);
    System.out.println(cls);  // class com.test.Car

对于基本数据类型,可以通过Class cls = 基本数据类型.class的方式,获取到其对应的Class对象

java
Class<Integer> integerClass = int.class;
Class<Boolean> booleanClass = boolean.class;
System.out.println(integerClass);   // int

对于基本数据类型对应的包装类,可以通过Class cls = 包装类.TYPE得到对应的Class类对象

java
Class<Integer> type = Integer.TYPE;
System.out.println(type);   // int

类加载

反射机制是Java实现动态语言的关键,也就是通过反射实现类动态加载

  • 静态加载:编译时加载相关的类,不管运行的时候是不是要使用到这个类,如果没有则报错,依赖性太强(传统的通过类实例化对象是静态加载,需要在代码中编写对应的类,才能在编译的过程中进行加载)
  • 动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,也不报错,降低了依赖性(通过反射进行动态加载,没有编写对应的类不会报错,只有当动态的加载该类时,运行到当前代码时,发现这个类没有,才会报错)

动态加载可以理解为一种延迟的加载,而静态加载,可以理解为一种在编译的时候进行加载,在运行时的时候就已经提前加载进去了

类加载时机:

  • 当创建对象(new)时(静态加载)
  • 当子类被加载时(静态加载)
  • 调用类中的静态成员时(静态加载)
  • 通过反射(动态加载)

类加载流程图:

image-20250429132927892

类加载的三个阶段的具体解释:面试中很可能被问到

  • 加载过程:生成ClassLoader进行类加载(将类的class文件读入内存,并为之创建一个java.lang.Class对象,此过程由类加载器完成)

  • 加载完后进行一个连接的过程:(将类的二进制数据合并到JRE(可运行环境)中)

    • 验证过程主要涉及到对文件进行安全的验证,如文件格式是否正确,元数据是否正确,字节码是否正确等

    • 准备阶段是对我们的静态变量、分配内存(在方法区进行内存分配)进行默认的初始化

    • 解析阶段是指虚拟机将常量池中的符号引用替换为直接引用

  • 初始化(指定初始化)阶段(JVM负责对类进行初始化,这里主要是指静态成员):在初始化中会真正的执行在类中去定义的Java代码(如:静态代码块、静态变量的赋值等,初始化在代码中写的内容)

  • 前两个阶段,是由JVM机去控制的,程序员是无法控制的,第三个阶段的初始化,是程序员可以去控制的(真正在类中定义的内容)

当类加载完成后,内存中就会产生两个重要的部分:在方法区中,会将类的字节码文件以二进制的形式保存起来(源数据,类中真正定义的方法,访问权限和变量的描述信息等);同时,还会在堆区生成字节码对应的数据结构,即类的Class对象(数据结构,是数据的访问入口),两个部分之间还有一个引用,这里就体现出了反射机制

加载阶段

JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、也可能是jar包,甚至网络)转化为二进制字节流加载到内存中(将某个类的字节码二进制数据/元数据加载到方法区),并生成一个代表该类的java.lang.Class对象,该对象是以字节码二进制数据为标准的

验证阶段

验证阶段目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全,包括文件格式验证(是否以魔数oxcafebabe开头、元数据验证、字节码验证和符号引用验证)

可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间

准备阶段

JVM会在该阶段对静态变量,分配内存进行默认初始化(对应数据类型的默认初始值,如0、null、false等)。这些变量所使用的内存都将在方法区中进行分配

java
class A {
    public int n1 = 10;   // n1是实例属性,不是静态变量,因此在准备阶段,是不会分配内存
    public static int n2 = 20;   // n2是静态变量,分配内存  n2在准备阶段被默认初始化为0
    // n3 是static final,是常量,与静态变量有区别,一旦赋值就不会改变,因此在准备阶段就被赋值为30
    public static final int n3 = 30;
}

解析阶段

解析阶段是虚拟机将常量池内的符号引用替换为直接引用(真正的内存地址引用)的过程

初始化阶段

到了初始化阶段,才是真正开始执行类中定义的Java程序代码,此阶段是执行<client>()方法的过程,<client>()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句,并进行合并

java
// 类加载的初始化阶段演示
public class ClassLoad {
    public static void main(String[] args) {
        // 执行过程:
        // 1.加载B类,并生成B的class对象
        // 2.准备阶段 num=0
        // 3.初始化阶段:通过clinit()方法,依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句
        /*
        	client() {
        		System.out.println("B的静态代码块被执行");
        		num = 300;
        		num = 100;
        	}
        	收集完后,并合并,将重复的语句进行合并,合并完的内容为:
        	client() {
        		System.out.println("B的静态代码块被执行");
        		num = 100;  // 合并完之后,最后num变成了100
        	}
        */
        System.out.println(B.num);  // 100  调用静态方法,构造器是不会执行的(但是直接使用类的静态属性,也会导致类的加载)
    }
}

class B {
    static {
        System.out.println("B的静态代码块被执行");
        num = 300;
    }
    static int num = 100;
    public B() {
        System.out.println("B()构造器被执行");
    }
}

虚拟机会保证一个类的<client>()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<client>()方法,其他线程都需要阻塞等待,直到活动线程执行<client>()方法完毕


通过反射获取类的结构信息

一旦我们获取到某个类的Class对象,我们就可以通过相应的API,拿到这个类的所有结构信息,常用API有(详细具体的方法查看java.lang.Class类):

java
public void api() {
    Class<?> personCls = Class.forName("com.test.Person");
    // 测试方法
    ...
}

class A {
    public String hobby;
    public void hi() {}
    public A() {}
}

interface IA {}
interface IB {}

@Deprecated
class Person extends A implements IA, IB {
    // 属性
    public String name;
    protected int age;
    String job;
    private double sal;
    // 方法
    public void m1(String name) {}
    protected void m2() {}
    void m3() {}
    private void m4() {}
    // 构造器
    public Person() {}
    public Person(String name) {}
    private Person(String name, int age) {}
}
  • getName:获取全名类

    java
    System.out.println(personCls.getName());   // com.test.Person
  • getSimpleName:获取简单类名

    java
    System.out.println(personCls.getSimpleName());  // Person
  • getFields:获取所有public修饰的属性,包含本类以及父类的

    java
    Field[] fields = personCls.getFields();
    for(Field field: fields) {
        System.out.println(field.getName());  // name hobby
    }
  • getDeclaredFields:获取本类中所有属性(不包含父类的)

    java
    Field[] declaredFields = personCls.getDeclaredFields();
    for(Field declaredField: declaredFields) {
        System.out.println(declaredField.getName());  // name age job sal
    }
  • getMethods:获取所有public修饰的方法,包含本类以及父类的(还有Object基类的公有方法)

    java
    Method[] methods = personCls.getMethods();
    for(Method method: methods) {
        System.out.println(method.getName());  // m1 hi ...(Object基类的公有方法)
    }
  • getDeclaredMethods:获取本类中所有方法(不包含父类的)

    java
    Method[] declaredMethods = personCls.getDeclaredMethods();
    for(Method declaredMethod: declaredMethods) {
        System.out.println(declaredMethod.getName());  // m1 m2 m3 m4
    }
  • getConstructors:获取本类所有public修饰的构造器

    java
    Constructor<?>[] constructors = personCls.getConstructors();
    for(Constructor<?> constructor: constructors) {
        // 这里只是输出构造名称,不显示形参
        System.out.println(constructor.getName());  // com.test.Person com.test.Person
    }
  • getDeclaredConstructors:获取本类中所有构造器

    java
    Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
    for(Constructor<?> declaredConstructor: declaredConstructors) {
        // 这里只是输出构造名称,不显示形参
        System.out.println(declaredConstructor.getName()); 
        // com.test.Person com.test.Person com.test.Person
    }
  • getPackage:以Package形式返回包信息

    java
    System.out.println(personCls.getPackage());   // com.test
  • getSuperClass:以Class形式返回父类信息

    java
    Class<?> superClass = personCls.getSuperClass();
    System.out.println(superClass);  // com.test.A
  • getInterfaces:以Class[]形式返回接口信息

    java
    Class<?>[] interfaces = personCls.getInterfaces();
    for(Class<?> interface: interfaces) {
        System.out.println(interface); // com.test.IA  com.test.IB
    }
  • getAnnotations:以Annotation[]形式返回注解信息

    java
    Annotation[] annotations = personCls.getAnnotations();
    for(Annotation annotation: annotations) {
        System.out.println(annotation); // @java.lang.Deprecated()
    }

关于字段类的常用API:(具体详见java.lang.reflect.Field

  • getModifiers:以int形式返回修饰符

    默认修饰符是0,public修饰符是1,private是2,protected是4,static是8,final是16

    如果属性是用public static进行修饰的,返回的值是两者相加,也就是9

    java
    Field[] declaredFields = personCls.getDeclaredFields();
    for(Field declaredField: declaredFields) {
        System.out.println(declaredField.getName() + ":" + declaredFields.getModifiers()); 
        // name:1 age:4 job:0 sal:2
    }
  • getType:以Class形式返回类型

    java
    Field[] declaredFields = personCls.getDeclaredFields();
    for(Field declaredField: declaredFields) {
        System.out.println(declaredField.getName() + ":" + declaredFields.getType());  
        // name:class java.lang.String age:int job:class java.lang.String sal:double
    }
  • getName:返回属性名

    java
    Field[] declaredFields = personCls.getDeclaredFields();
    for(Field declaredField: declaredFields) {
        System.out.println(declaredField.getName());  // name age job sal
    }

关于方法类的常用API:(具体详见java.lang.reflect.Mrthod

  • getModifiers:以int形式返回修饰符

    默认修饰符是0,public修饰符是1,private是2,protected是4,static是8,final是16

    如果方法是用public static进行修饰的,返回的值是两者相加,也就是9

    java
    Method[] declaredMethods = personCls.getDeclaredMethods();
    for(Method declaredMethod: declaredMethods) {
        System.out.println(declaredMethod.getName() +":"+ declaredMethod.getModifiers());     // m1:1 m2:4 m3:2 m4:0
    }
  • getRerurnType:以Class形式获取返回类型

    java
    Method[] declaredMethods = personCls.getDeclaredMethods();
    for(Method declaredMethod: declaredMethods) {
        System.out.println(declaredMethod.getName() +":"+ declaredMethod.getRerurnType());     // m1:void m2:void m3:void m4:void
    }
  • getName:返回方法名

    java
    Method[] declaredMethods = personCls.getDeclaredMethods();
    for(Method declaredMethod: declaredMethods) {
        System.out.println(declaredMethod.getName());  // m1 m2 m3 m4
    }
  • getParameterTypes:以Class[]返回参数类型数组

    java
    Method[] declaredMethods = personCls.getDeclaredMethods();
    for(Method declaredMethod: declaredMethods) {
        System.out.println(declaredMethod.getName());  // m1 m2 m3 m4
        // 输出当前方法的形参数组情况
        Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
        for(Class<?> parameterType: parameterTypes) {
            System.out.println(parameterType);  // class java.lang.String (m1方法中的形参)
        }
    }

关于构造器类的常用API:(具体详见java.lang.reflect.Constructor

  • getModifiers:以int形式返回修饰符

    默认修饰符是0,public修饰符是1,private是2,protected是4,static是8,final是16

    如果方法是用public static进行修饰的,返回的值是两者相加,也就是9

  • getName:返回构造器名(全类名)

  • getParameterTypes:以Class[]返回参数(形参)类型数组


通过反射创建对象

通过反射创建对象有两种方式:

  • 调用类中的public修饰的无参构造器
  • 调用类中的指定构造器

其中两种通过反射创建对象的方式会涉及到以下的一些方法:

  • Class类相关方法:
    • newInstance:调用类中的无参构造器(该无参构造器要求是public的),获取对应类的对象
    • getConstructor(Class...clazz):根据参数列表,获取对应的public构造器对象
    • getDecalaredConstructor(Class...clazz):根据参数列表,获取对应构造器(所有的构造器对象都可以获取,根据参数列表获取即可)对象
  • Constructor类相关方法:
    • setAccessible:暴破(使用反射可以访问private的构造器或其他的私有方法和属性)
    • newInstance(Object...obj):调用构造器

示例演示:

java
public class ReflecCreateInstance {
    public static void main(String[] args) throws Exception {
        // 获取到User类的Class对象
        Class<?> userClass = Class.forName("com.test.reflection.User");
        // 通过public的无参构造器创建实例
        Object o = userClass.newInstance();
        System.out.println(o);   // User {age=25, name=jlc}
        // 通过public的有参构造器创建实例
        Constructor<?> constructor = userClass.getConstructor(String.class);
        /*
        	constructor对象就是
        	public User(String name) {  // public有参构造器
                this.name = name;
            }
        */
        Object o2 = constructor.newInstance("JLC");
        System.out.println(o2);   // User {age=25, name=JLC}
        // 通过private的有参构造器创建实例
        Constructor<?> constructor2 = userClass.getDecalaredConstructor(int.class, String.class);
        constructor2.setAccessible(true);
        // 这时会出现一个非法的访问异常,因为构造器是私有的,我们需要在前面加上暴破的声明
        Object o3 = constructor2.newInstance(18, "JLC");  
        System.out.println(o3);   // User {age=18, name=JLC}
    }
}

class User {
    private int age = 25;
    private String name = "jlc";
    // 构造器
    public User() {}
    public User(String name) {
        this.name = name;
    }
    private User(int age, String name) {
        this.age = age;
        this.name = name;
    }
    
    public String toString() {
        return "User {age=" + age + ", name=" + name + "}";
    }
}

通过反射访问类中的属性

  • 通过反射访问类中的属性(公有的)

    1. 根据属性名获取Field对象:Field f = class对象.getField("属性名);

    2. 访问属性

      • 获取属性值:f.get(o)
      • 设置(修改)属性值:f.set(o, 值);

      如果是静态属性,则setget中的参数o,可以写为null

  • 通过反射访问类中的属性(私有的)

    1. 根据属性名获取Field对象:Field f = class对象.(属性名);

    2. 暴破:f.setAccessible(true); 其中fField(使用了暴破。私有的成员也可以访问到)

    3. 访问成员

      • 获取属性值:f.get(o)
      • 设置(修改)属性:f.set(o, 值);

      如果是静态属性,则setget中的参数o,可以写为null

案例演示:

java
public class ReflecCreateProperty {
    public static void main(String[] args) throws Exception {
		// 得到Student类对应的Class对象
        Class<?> stuClass = Class.forName("com.test.reflection.Student");
        // 创建对象
        Object o = stuClass.newInstance();  // o的运行类型就是Student
        
        // 使用反射得到age属性(公有的)象
        Field age = stuClass.getField("age");
        age.set(o, 88);  // 通过反射来操作属性(设置属性值)
        System.out.println(o);   // Student {age=88, name=null}
        System.out.println(age.get(o));   // 88  直接返回age属性的值
        
        // 使用反射得到name属性(私有的)象
        Field name = stuClass.getDeclaredField("name");
        name.setAccessible(true);  // 暴破操作
        name.set(o, "jlc");  // 设置属性值  name是静态方法,可以简写为 name.set(null, "jlc");
        System.out.println(o);   // Student {age=88, name=jlc}
    }
}

class Student {
    public int age = 25;
    private static String name = "jlc";
    public Student() {}

    public String toString() {
        return "Student {age=" + age + ", name=" + name + "}";
    }
}

通过反射访问类中的方法

通过反射访问类中的方法

  1. 根据方法名和参数列表获取Method方法对象:

    • 私有方法的获取:Method m = class对象.getDeclaredMethod(方法名, XX.class);

    • 公有方法的获取:Method m = class对象.getMethod(方法名, XX.class);

      私有方法的获取是获取所有的方法,当然也可以获取公有的方法;但是公有方法的获取不能获取私有方法

  2. 获取对象:Object o = class对象.newInstance();

    在反射中,如果方法有返回值,统一返回Object类型(编译类型),但运行类型还是对应函数的返回值类型

  3. 暴破(访问私有的方法需要进行暴破):m.setAccessible(true);

  4. 访问:Object returnValue = m.invoke(o, 实参列表);

    注意:如果是静态方法,则invoke的参数o,可以写为null

案例演示:

java
public class ReflecCreateMethod {
    public static void main(String[] args) throws Exception {
		// 得到Student类对应的Class对象
        Class<?> bossClass = Class.forName("com.test.reflection.Boss");
        // 创建对象
        Object o = bossClass.newInstance();
        
        // 调用public的hi方法
        Method hi = bossClass.getMethod("hi", String.class);
        hi.invoke(o, "jlc");   // jlc
        
        // 调用private的say方法
        Method say = bossClass.getDeclaredMethod("say", int.class, String.class, char.class);
        say.setAccessible(true);
        System.out.println(say.invoke(o, 25, "jlc", '男'));   // 25 jlc 男
        // say方法是静态方法,还可以简化的调用 say.invoke(null, 25, "jlc", '男');
    }
}

class Boss {
    public int age;
    private static String name;
    public Boss() {}
	// 静态方法
    private static String say(int n, String s, char c) {
        return n + " " + s + " " + c;
    }
    // 普通公有方法
    public void hi(String s) {
        System.out.println(s);
    }
}

Released under the MIT License.