Java-反射机制详解

1. 反射机制概述

1.1 Java反射机制

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

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

在这里插入图片描述

1.2 Java反射机制提供的功能

(1) 在运行时判断任意一个对象所属的类
(2) 在运行时构造任意一个类的对象
(3) 在运行时判断任意一个类所具有的成员变量和方法
(4) 在运行时获取泛型信息
(5) 在运行时调用任意一个对象的成员变量和方法
(6) 在运行时处理注解
(7) 生成动态代理

1.3 反射相关主要的API

java.lang.Class:代表一个类
java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器

2. Class类

2.1 如何理解Class类

类的加载过程
代码经过 javac 命令编译之后,会生成一个或者多个字节码文件(.class文件)。接着使用 java 命令对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中,此过程就被称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类就作为 Class 类型的一个实例。

例如:Class<Person> clazz = Person.class; 就相当于 Person.java 经过编译之后的 Person.class 文件被加载到内存以后的形态,里面包含了 Person 类的各种构造器、属性、方法。而 Person.class 本身又是 java.lang.Class 的一个实例

2.2 Class类实例的获取方式

注意:运行时类本身是一个单例的,尽管运行时类实例的获取方式有多种,但通过不同方式获得的Class 实例都是同一个运行时类【注意是获得,不是创建】。

@Test
public class Main {
	public void test() throws Exception {
		// 方式1:调用运行时类的属性 .class
		// 泛型选写,加上泛型可以避免后续需要强转
		Class<Person> clazz1 = Person.class;
		
		// 方式2:通过运行时类的对象,调用getClass()方法
		Person p1 = new Person();
		Class clazz2 = p1.getClass();
		
		// 方式3:调用 Class的静态方法 forName(类的全路径) 【最常用】
		Class clazz3 = Class.forName("com.zju.reflect.Person");
	
		// 方式4:使用类的加载器ClassLoader【了解】
		ClassLoader classLoader = Main.class.getClassLoader();
		Class clazz4 = classLoader.loadClass("com.zju.reflect.Person");
		
		// 注意:clazz1 - class4 都是同一个实例
	}
}

2.3 哪些类型可以有Class对象实例

Class:外部类、成员(成员内部类、静态内部类)、局部内部类、匿名内部类
interface:接口类型
[]:数组类型
enum:枚举类型
annotation:注解类型
primitive type:基本数据类型
void:返回值类型

3. 类的加载与类加载器

3.1 类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤对该类进行初始化:

步骤一:类的加载
调用java命令将编译后的 .class 字节码文件读入内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.Class 对象,作为方法区中数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个 Class 对象。这个加载的过程需要类加载器 ClassLoader 参与。

步骤二:类的链接
将Java 类的二进制代码合并到 JVM的运行状态中的过程。
(1) 验证:确保加载的类信息复合JVM 规范,例如:以 cafe 开头,没有安全方面的问题。
(2) 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
(3) 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

步骤三:类的初始化
(1) 执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造类对象的构造器)。
(2) 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发器父类的初始化。
(3) 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。

3.2 类加载器的作用

(1) 类加载器可以加载类:将 .class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区运行时的数据结构,然后在堆中生成一个代表这个类的 java.lang.Class 对象,作为方法区中类数据的访问入口。
(2) 类加载器可以进行类缓存:标准的 JavaSE 类加载器可以按要求查找类,一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM 垃圾回收机制可以回收这些 Class 对象。

3.3 类加载器的划分

Bootstrap ClassLoader:引导类加载器:用C++ 编写的,是JVM 自带的类加载器,负责加载Java 核心类库中的类,该加载器无法直接获取。
Extension ClassLoader:扩展类加载器:负责 jre/lib/ext 目录下的 jar包或 -D java.ext.dirs 指定目录下的 jar包装入工作库。
System ClassLoader:系统类加载器:负责 java -classpath 或 -D java.class.path 所指的目录下的类与 jar包装入工作,是最常用的加载器
自定义类加载器:程序员自己定义的类加载器,注意:不是负责加载我们自己定义的类的加载器,是自定义的类加载器。

不同的类加载器负责加载不同类型的类,例如引导类加载器 就负责加载 String、Integer这种系统内置类,我们无法让引导类加载器帮助我们去加载我们自己定义的类、我们也无法直接获取引导类加载器。像我们自己定义的类,比如 Person 类,都是System ClassLoader 系统类加载器帮我们加载的。

这里可以简单的演示一下:

public class Main {
    @Test
    public void test() {

        // 我们自己定义的类,都是使用【系统类加载器】
        ClassLoader systemClassLoader = Main.class.getClassLoader();
        System.out.println(systemClassLoader);  // 输出: sun.misc.Launcher$AppClassLoader@18b4aac2

        // 【系统类加载器】通过 getParent 方法可以获得【扩展类加载器】
        ClassLoader extensionClassLoader = systemClassLoader.getParent();
        System.out.println(extensionClassLoader); // 输出:sun.misc.Launcher$ExtClassLoader@38af3868

        //【引导类加载器】由C++实现,程序员无法直接访问
        ClassLoader bootstrapClassLoader = extensionClassLoader.getParent();
        System.out.println(bootstrapClassLoader); // 输出:null

		// Java 自带的类 都是使用的【引导类加载器】,程序员无法直接访问
        System.out.println(String.class.getClassLoader()); // 输出:null
    }
}

注意:这里调用类加载器的 getParent() 加载器可以获得更底层的加载器,但是命名有误导性,让这些加载器看起来好像有继承关系,但是不同层次的加载器之间并不是传统意义上的继承关系,实际上只是体现一种规范的思想。

3.4 使用类加载器加载配置文件

我们知道 Properties 类使用来读取配置文件的,我们先用传统的IO 方式演示一下读取配置文件,然后再演示一下如何使用 类加载器 去加载配置文件。

此时,我们的jdbc.properties 文件是直接放在项目下的,没有放在 src 路径下,程序可以正常读取。

@Test
public void testProperties() throws Exception {
    Properties properties = new Properties();
    properties.load(new FileInputStream("jdbc.properties"));
    String username = properties.getProperty("username");
    String password = properties.getProperty("password");
    
    System.out.println(username + " " + password);
}

但是以后当我们开发 web 项目的时候,代码打包时配置文件如果放在项目路径下的话配置文件是会丢失的,因此我们需要将 properties 文件放入 src 路径下。现在,jdbc.properties 文件放在 src 路径下。

那么我们传统的 IO 读取方式就要修改路径为:

@Test
public void testProperties() throws Exception {
    Properties properties = new Properties();
    properties.load(new FileInputStream("src\\jdbc.properties"));
    String username = properties.getProperty("username");
    String password = properties.getProperty("password");
    
    System.out.println(username + " " + password);
}

其实,我们可以使用类加载器去获得在src 路径下的配置文件 IO 流,取代 FileInputStream 去加载配置文件。

@Test
public void testProperties() throws Exception {
    Properties properties = new Properties();
    // 使用类加载器获得 src下文件输入流
    ClassLoader classLoader = Main.class.getClassLoader();
    InputStream inputStream = classLoader.getResourceAsStream("jdbc.properties");
    properties.load(inputStream);

    String username = properties.getProperty("username");
    String password = properties.getProperty("password");

    System.out.println(username + " " + password);
}

两种方式都可以获得 src 路径下的配置文件,我们可以选择一种我们喜欢的方式。

4. 创建运行时类的对象

运行时类.newInstance():调用此方法,创建运行时类的对象。要求:运行时类必须提供无参的构造器,无参构造器的访问权限可以访问到,通常设为 public。

@Test
public void testNewInstance() throws Exception {
    Class<Person> clazz = Person.class;
    // 使用 newInstance() 生成对应类的运行时的对象
    Person person = clazz.newInstance();
}

多插一句:在javabean 中要求提供一个public 的无参构造器,原因:
(1) 便于通过反射创建运行时类的对象
(2) 便于子类继承此类时,默认调用super(),保证父类有次构造器

5. 获取运行时类的完整信息

在实际开发中,我们不会去获取一个类的所有结构,这里仅仅作为一个介绍。

首先,为了方便我们后面的演示,我们创建一个“异常丰富”的类Person,方便之后我们演示:

Person 类的父类 Creature

package com.zju.bean;

import java.io.Serializable;

public class Creature<T> implements Serializable {
    private char gender;
    public double weight;
    private void breath() {
        System.out.println("生物呼吸...");
    }
    public void eat() {
        System.out.println("生物吃东西...");
    }
}

演示自定义的一个接口:MyInterface

package com.zju.bean;

public interface MyInterface {
    void info();
}

自定义的一个注解 MyAnnotation

package com.zju.bean;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "hello";
}

核心演示类Person

package com.zju.bean;

@MyAnnotation(value = "hi")
public class Person extends Creature<String> implements Comparable<String>, MyInterface {
    private String name;
    int age;
    public int id;
    public Person() {}
    @MyAnnotation(value = "abc")
    public Person(String name) {
        this.name = name;
    }
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @MyAnnotation
    private String show(String nation, int age) throws ArithmeticException {
        System.out.println("我是国籍是:" + nation + ",我" + age + "岁...");
        return nation;
    }
    public String display(String interests) throws ClassNotFoundException, NullPointerException {
        return interests;
    }
    @Override
    public void info() {
        System.out.println("我是一个人...");
    }
    @Override
    public int compareTo(String o) {
        return 0;
    }
}

5.1 获取当前运行时类的所有属性信息

运行时类.getFields():获取当前运行时类及其所有父类中声明为 public 访问权限的属性。

@Test
public void testField() {
    Class clazz = Person.class;

    // 获取当前运行时类及其所有父类中声明为 `public` 访问权限的属性
    Field[] fields = clazz.getFields();
    Arrays.asList(fields).forEach(System.out::println);
}

运行结果为:

public int com.zju.bean.Person.id
public double com.zju.bean.Creature.weight

Process finished with exit code 0

运行时类.getDeclaredFields():获取当前运行时类自己声明的所有权限的属性,不包含父类中任何属性。

@Test
public void testField() {
    Class clazz = Person.class;

    // 获取当前运行时类自己声明的所有权限的属性,不包含父类中任何属性
    Field[] fields = clazz.getDeclaredFields();
    Arrays.asList(fields).forEach(System.out::println);
}

运行结果为:

private java.lang.String com.zju.bean.Person.name
int com.zju.bean.Person.age
public int com.zju.bean.Person.id

Process finished with exit code 0

同时,我们还可以具体看每个变量的修饰符、数据类型、变量名等具体信息:

@Test
public void testField() {
    Class clazz = Person.class;

    // 获取当前运行时类自己声明的所有权限的属性
    Field[] fields = clazz.getDeclaredFields();
    Arrays.asList(fields).forEach(field -> {
        // 1. 权限修饰符
        int modifier = field.getModifiers();
        System.out.print("访问修饰符:" + Modifier.toString(modifier) + "\t");

        // 2. 数据类型
        Class type = field.getType();
        System.out.print("数据类型:" + type.getName() + "\t");

        // 3. 变量名
        System.out.print("变量名:" + field.getName());

        System.out.println();
    });
}

运行结果为:

访问修饰符:private	数据类型:java.lang.String	变量名:name
访问修饰符:	数据类型:int	变量名:age
访问修饰符:public	数据类型:int	变量名:id

Process finished with exit code 0

5.2 获取当前运行时类的所有方法信息

运行时类.getMethods():获取当前运行时类及其所有父类中声明为 public 访问权限的方法。

@Test
public void testMethod() {
    Class clazz = Person.class;

    // 获取当前运行时类及其所有父类中声明为 `public` 访问权限的方法
    Method[] methods = clazz.getMethods();
    Arrays.asList(methods).forEach(System.out::println);
}

运行结果为:

public int com.zju.bean.Person.compareTo(java.lang.String)
public int com.zju.bean.Person.compareTo(java.lang.Object)
public void com.zju.bean.Person.info()
public java.lang.String com.zju.bean.Person.display(java.lang.String)
public void com.zju.bean.Creature.eat()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

Process finished with exit code 0

运行时类.getDeclaredMethods():获取当前运行时类自己声明的所有权限的方法,不包含父类中任何方法。

@Test
public void testMethod() {
    Class clazz = Person.class;

    // 获取当前运行时类自己声明的所有权限的方法,不包含父类中任何方法
    Method[] methods = clazz.getDeclaredMethods();
    Arrays.asList(methods).forEach(System.out::println);
}

运行结果为:

public int com.zju.bean.Person.compareTo(java.lang.String)
public int com.zju.bean.Person.compareTo(java.lang.Object)
public void com.zju.bean.Person.info()
public java.lang.String com.zju.bean.Person.display(java.lang.String)
private java.lang.String com.zju.bean.Person.show(java.lang.String)

Process finished with exit code 0

同时,我们还可以具体看每个方法的注解、修饰符、返回值类型、方法名、形参列表、异常列表等具体信息:

@Test
public void testMethod() {
    Class clazz = Person.class;

    // 获取当前运行时类自己声明的所有权限的方法,不包含父类中任何方法
    Method[] methods = clazz.getDeclaredMethods();
    Arrays.asList(methods).forEach(method -> {
        // 1. 获取方法声明的注解
        Annotation[] annotations = method.getAnnotations();
        System.out.print("注解:");
        Arrays.asList(annotations).forEach(annotation -> System.out.print(annotation + "\t"));

        // 2. 获取方法的权限修饰符
        System.out.print("权限修饰符:" + Modifier.toString(method.getModifiers()) + "\t");

        // 3. 获取方法的返回值类型
        System.out.print("返回值类型:" + method.getReturnType().getName() + "\t");

        // 4. 获取方法名
        System.out.print("方法名:" + method.getName() + "\t");

        // 5. 获取形参列表(只能获取形参类型)
        System.out.print("形参列表:[");
        Class[] paraTypes = method.getParameterTypes();
        for (int i = 0; i < paraTypes.length; i ++) {
            System.out.print(paraTypes[i].getName());
            if (i != paraTypes.length - 1) {
                System.out.print(", ");
            }
        }
        System.out.print("]" + "\t");

        // 6. 获取抛出的异常列表(只能获取异常类型)
        System.out.print("抛出的异常列表:[");
        Class[] exceptTypes = method.getExceptionTypes();
        for (int i = 0; i < exceptTypes.length; i ++) {
            System.out.print(exceptTypes[i].getName());
            if (i != exceptTypes.length - 1) {
                System.out.print(", ");
            }
        }
        System.out.print("]" + "\t");

        System.out.println();
    });
}

运行结果为:

注解:权限修饰符:public	返回值类型:int	方法名:compareTo	形参列表:[java.lang.String]	抛出的异常列表:[]	
注解:权限修饰符:public volatile	返回值类型:int	方法名:compareTo	形参列表:[java.lang.Object]	抛出的异常列表:[]	
注解:权限修饰符:public	返回值类型:void	方法名:info	形参列表:[]	抛出的异常列表:[]	
注解:@com.zju.bean.MyAnnotation(value=hello)	权限修饰符:private	返回值类型:java.lang.String	方法名:show	形参列表:[java.lang.String, int]	抛出的异常列表:[java.lang.ArithmeticException]	
注解:权限修饰符:public	返回值类型:java.lang.String	方法名:display	形参列表:[java.lang.String]	抛出的异常列表:[java.lang.ClassNotFoundException, java.lang.NullPointerException]	

Process finished with exit code 0

5.3 获取当前运行时类的所有构造器信息

运行时类.getConstructors():获取当前运行时类中声明为 public 的构造器。

@Test
public void testConstructor() {
    Class clazz = Person.class;
    // 获取当前运行时类中声明为 `public` 的构造器
    Arrays.asList(clazz.getConstructors()).forEach(System.out::println);
}

运行结果为:

public com.zju.bean.Person(java.lang.String)
public com.zju.bean.Person()

Process finished with exit code 0

运行时类.getDeclaredConstructors():获取当前运行时类中声明为 public 的构造器。

@Test
public void testConstructor() {
    Class clazz = Person.class;
    // 获取当前运行时类中所有声明的构造器
    Arrays.asList(clazz.getDeclaredConstructors()).forEach(System.out::println);
}

运行结果为:

com.zju.bean.Person(java.lang.String,int)
public com.zju.bean.Person(java.lang.String)
public com.zju.bean.Person()

Process finished with exit code 0

5.4. 获取运行时类的父类及父类的泛型

方法比较简单,直接看代码和注释:

@Test
public void testParents() {
    Class clazz = Person.class;

    // 获取运行时类的父类
    Class superclass = clazz.getSuperclass();
    System.out.println("运行时类的父类:" + superclass);

    // 获取运行时类的带泛型的父类
    Type genericSuperclass = clazz.getGenericSuperclass();
    System.out.println("运行时类的带泛型的父类:" + genericSuperclass);

    // 获取运行时类的带泛型的父类的泛型
    ParameterizedType genericType = (ParameterizedType) genericSuperclass;
    Type[] arguments = genericType.getActualTypeArguments();
    System.out.print("运行时类的带泛型的父类的泛型:[");
    for (int i = 0; i < arguments.length; i ++) {
        // 获得到具体泛型的类
        Class clz = (Class) arguments[i];
        System.out.print(clz.getName());
        if (i != arguments.length - 1) {
            System.out.print(", ");
        }
    }
    System.out.println("]");
}

运行结果为:

运行时类的父类:class com.zju.bean.Creature
运行时类的带泛型的父类:com.zju.bean.Creature<java.lang.String>
运行时类的带泛型的父类的泛型:[java.lang.String]

Process finished with exit code 0

5.5. 获取运行时类及其父类实现的接口

方法比较简单,直接看代码和注释:

@Test
public void testInterface() {
    Class clazz = Person.class;

    // 获取运行时类自己实现的接口
    System.out.println("运行时类自己实现的接口:");
    Class[] interfaces = clazz.getInterfaces();
    Arrays.asList(interfaces).forEach(System.out::println);
    
    // 获取运行时类的父类实现的接口
    System.out.println("运行时类的父类实现的接口:");
    Class superclass = clazz.getSuperclass();
    Class superInterfaces = superclass.getSuperclass();
    Arrays.asList(superInterfaces).forEach(System.out::println);
}

运行结果为:

运行时类自己实现的接口:
interface java.lang.Comparable
interface com.zju.bean.MyInterface
运行时类的父类实现的接口:
class java.lang.Object

Process finished with exit code 0

5.6. 获取运行时类所在的包

方法比较简单,直接看代码和注释:

@Test
public void testPackage() {
    Class clazz = Person.class;

    // 获取运行时类所在的包
    Package _package = clazz.getPackage();
    System.out.println(_package);
}

运行结果为:

package com.zju.bean

Process finished with exit code 0

5.7. 获取运行时类声明的注解

方法比较简单,直接看代码和注释:

@Test
public void testAnnotation() {
    Class clazz = Person.class;

    // 获取运行时类声明的注解
    Arrays.asList(clazz.getAnnotations()).forEach(System.out::println);
}

运行结果为:

@com.zju.bean.MyAnnotation(value=hi)

Process finished with exit code 0

6. 调用运行时类的指定信息

6.1 调用运行时类的指定属性

运行时类.getField("属性名"):获取运行时类及其父类的 public 权限属性字段对象。注意:仅能获得 public 权限的,默认权限也无法获得。
Field字段实例.set(运行时类实例, 属性值):设置某运行时类实例的指定属性。
Field字段实例.get(运行时类实例, 属性名):获得某运行时类实例的指定属性。

@Test
public void testCertainField() throws Exception {
    Class clazz = Person.class;

    // 创建运行时类的对象
    Person p = (Person) clazz.newInstance();

    // 获取当前运行时类及其父类指定的 `public` 属性
    Field id = clazz.getField("id");

    /*
    * set():设置当前运行类属性的值
    * 参数1:指明设置哪个对象的属性
    * 参数2:将此属性值设置为多少
    */
    id.set(p, 1001);

    /*
     * get():获取当前运行类属性的值
     * 参数1:指明获取哪个对象的属性值
     */
    int pId = (int) id.get(p);
    System.out.println(pId);
}

运行结果为:

1001

Process finished with exit code 0

一般而言,我们不采用上述方法,因为 public 权限的属性并不多,我们用 getDeclaredField() 函数会更多一些:

运行时类.getDeclaredField("属性名"):获取当前运行时类声明的、任意权限的指定属性对象

对于 public 权限属性,需要设置 setAccessible(true) 开启暴力反射模式才可以对其进行访问和修改。

@Test
public void testCertainField() throws Exception {
    Class clazz = Person.class;

    // 创建运行时类的对象
    Person p = (Person) clazz.newInstance();

    // 获取当前运行时类声明的、任意权限的指定属性对象
    Field name = clazz.getDeclaredField("name");

    // 对非 `public` 属性,需要设置暴力反射才可以对其访问和修改
    name.setAccessible(true);

    /*
    * set():设置当前运行类属性的值
    * 参数1:指明设置哪个对象的属性
    * 参数2:将此属性值设置为多少
    */
    name.set(p, "tom");

    /*
     * get():获取当前运行类属性的值
     * 参数1:指明获取哪个对象的属性值
     */
    String pName = (String) name.get(p);
    System.out.println(pName);
}

6.2 调用运行时类的指定方法

运行时类.getMethod("方法名", 参数类型...):获取运行时类及其父类的 public 权限的方法对象,因为同名方法可能有很多(重载),还需要传入方法的参数类型列表。注意:仅能获得 public 权限的方法,默认权限也无法获得。
返回值 Method实例.invoke(运行时类实例, 参数列表)

@Test
public void testCertainMethod() throws Exception {
    Class clazz = Person.class;

    // 创建运行时类的对象
    Person p = (Person) clazz.newInstance();

    // 获取当前运行时类及其父类的 `public` 方法
    Method display = clazz.getMethod("display", String.class);

    /*
     * invoke():令该方法执行
     * 参数1:方法的调用者
     * 参数2:给方法形参赋值的实参列表
     */
    String interest = (String) display.invoke(p, "足球");
    System.out.println("返回值为:" + interest);
}

运行结果为:

返回值为:足球

Process finished with exit code 0

一般而言,我们不采用上述方法,因为我们想用的方法并不一定都是 public 的,我们用 getDeclaredMethod() 函数会更多一些:

运行时类.getDeclaredMethod("方法名", 参数类型...):获取当前运行时类声明的、任意权限的指定方法对象

对于 public 权限方法,需要设置 setAccessible(true) 开启暴力反射模式才可以对该方法进行调用执行。

@Test
public void testCertainMethod() throws Exception {
    Class clazz = Person.class;

    // 创建运行时类的对象
    Person p = (Person) clazz.newInstance();

    // 获取当前运行时类声明的、任意属性的方法
    Method show = clazz.getDeclaredMethod("show", String.class, int.class);

    // 对非 `public` 方法,需要设置暴力反射才可以对其进行调用执行
    show.setAccessible(true);

    /*
     * invoke():令该方法执行
     * 参数1:方法的调用者
     * 参数2:给方法形参赋值的实参列表
     */
    String nation = (String) show.invoke(p, "中国", 21);
    System.out.println("返回值为:" + nation);
}

运行结果为:

我是国籍是:中国,我21岁...
返回值为:中国

Process finished with exit code 0

最后,再演示一下如何调用静态方法
我们先在 Person 类中加入一个静态方法 private static void showDesc();

@MyAnnotation(value = "hi")
public class Person extends Creature<String> implements Comparable<String>, MyInterface {
    private String name;
    int age;
    public int id;
    public Person() {}
    @MyAnnotation(value = "abc")
    public Person(String name) {
        this.name = name;
    }
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @MyAnnotation
    private String show(String nation, int age) throws ArithmeticException {
        System.out.println("我是国籍是:" + nation + ",我" + age + "岁...");
        return nation;
    }
    public String display(String interests) throws ClassNotFoundException, NullPointerException{
        return interests;
    }
    private static void showDesc() {
        System.out.println("欢迎来到浙江大学...");
    }
    @Override
    public void info() {
        System.out.println("我是一个人...");
    }
    @Override
    public int compareTo(String o) {
        return 0;
    }
}

对于静态方法invoke 函数中的第一个参数,传入Person.class 或者 null 都可调用,若方法无返回值,则 invoke 方法返回 null。当然,静态属性也是如此。

@Test
public void testCertainMethod() throws Exception {
    Class clazz = Person.class;

    // 创建运行时类的对象
    Person p = (Person) clazz.newInstance();

    // 获取当前运行时类声明的、任意属性的静态方法
    Method showDesc = clazz.getDeclaredMethod("showDesc");

    // 对非 `public` 方法,需要设置暴力反射才可以对其进行调用执行
    showDesc.setAccessible(true);

    /*
     * invoke():令该方法执行
     * 参数1:方法的调用者
     * 参数2:给方法形参赋值的实参列表
     */
    // Object returnType = showDesc.invoke(Person.class);
    Object returnType = showDesc.invoke(null);
    System.out.println("返回值为:" + returnType);
}

返回值为:

欢迎来到浙江大学...
返回值为:null

Process finished with exit code 0

6.3 调用运行时类的指定构造器

这里比较好理解,直接上代码和注释:

@Test
public void testCertainConstructor() throws Exception {
    Class clazz = Person.class;

    // 获取该类声明的构造器,参数为指定构造器的参数列表
    Constructor constructor = clazz.getDeclaredConstructor(String.class);

    // 使用爆破反射,确保该构造器可访问【构造器可能私有】
    constructor.setAccessible(true);

    // 通过 newInstance() 方式,结合参数列表通过指定构造器生成对象
    Person person = (Person) constructor.newInstance("小明");

    System.out.println(person);
}

输出结果为:

com.zju.bean.Person@593634ad

Process finished with exit code 0

注意:该方法使用的比较少,一般而言使用 clazz.newInstance() 调用无参构造器生成对象的的概率最大,很少使用指定构造器生成对象,因为这样不通用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铁头娃撞碎南墙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值