Java 反射机制详解:入门、使用、实例

简介: Java 反射机制详解:入门、使用、实例

文章目录

反射概述

什么是反射

使用反射的优缺点

Class 对象的获取及使用

获取 Class 对象的方式

Class 对象的使用

获取成员变量

获取构造方法

获取成员方法

获取类名

反射实例

总结

反射概述

什么是反射

将类的各个组成部分封装为其他对象的过程就叫做 反射,其中 组成部分 指的是我们类的 成员变量(Field)、构造方法(Constructor)、成员方法(Method)。


使用反射的优缺点

优点


在程序运行过程中可以操作类对象,增加了程序的灵活性;

解耦,从而提高程序的可扩展性,提高代码的复用率,方便外部调用;

对于任何一个类,当知道它的类名后,就能够知道这个类的所有属性和方法;而对于任何一个对象,都能够调用它的一个任意方法。

缺点


性能问题:Java 反射中包含了一些动态类型,JVM 无法对这些动态代码进行优化,因此通过反射来操作的方式要比正常操作效率更低。

安全问题:使用反射时要求程序必须在一个没有安全限制的环境中运行,如果程序有安全限制,就不能使用反射。

程序健壮性:反射允许代码执行一些平常不被允许的操作,破坏了程序结构的抽象性,导致平台发生变化时抽象的逻辑结构无法被识别。

Class 对象的获取及使用

获取 Class 对象的方式

Class.forName("全类名")

源代码阶段,它能将字节码文件加载进内存中,然后返回 Class 对象,多用于 配置文件 中,将类名定义在配置文件中,通过读取配置文件来加载类。


类名.class

类对象阶段,通过类名的 class 属性来获取,多用于 参数的传递。


对象.getClass()

运行时阶段,getClass() 定义在 Object 类中,表明所有类都能使用该方法,多用于 对象的获取字节码 的方式。


我们首先定义一个 Person 类,用于后续反射功能的测试;


package com.cunyu;
/**
 * @author : cunyu
 * @version : 1.0
 * @className : Person
 * @date : 2021/4/7 22:37
 * @description : Person 类
 */
public class Person {
    private int age;
    private String name;
    public long id;
    public long grade;
    protected float score;
    protected int rank;
    public Person(int age, String name, long id, long grade, float score, int rank) {
        this.age = age;
        this.name = name;
        this.id = id;
        this.grade = grade;
        this.score = score;
        this.rank = rank;
    }
    public Person() {
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public long getGrade() {
        return grade;
    }
    public void setGrade(long grade) {
        this.grade = grade;
    }
    public float getScore() {
        return score;
    }
    public void setScore(float score) {
        this.score = score;
    }
    public int getRank() {
        return rank;
    }
    public void setRank(int rank) {
        this.rank = rank;
    }
    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("Person{");
        sb.append("age=").append(age);
        sb.append(", name='").append(name).append('\'');
        sb.append(", id=").append(id);
        sb.append(", grade=").append(grade);
        sb.append(", score=").append(score);
        sb.append(", rank=").append(rank);
        sb.append('}');
        return sb.toString();
    }
}

定义好 Person 类之后,我们尝试用 3 种不同的方式来获取 Class 对象,并比较它们是否相同。

package com.cunyu;
/**
 * @author : cunyu
 * @version : 1.0
 * @className : Demo1
 * @date : 2021/4/7 23:29
 * @description : Class 对象的获取
 */
public class Demo1 {
    public static void main(String[] args) throws ClassNotFoundException {
//        第一种方式,Class.forName("全类名")
        Class class1 = Class.forName("com.cunyu.Person");
        System.out.println(class1);
//        第二种方式,类名.class
        Class class2 = Person.class;
        System.out.println(class2);
//        第三种方式,对象.getName()
        Person person = new Person();
        Class class3 = person.getClass();
        System.out.println(class3);
//        比较三个对象是否相同
        System.out.println(class1 == class2);
        System.out.println(class1 == class3);
    }
}

image.png上述代码中,会发现最后输出的比较结果返回的是两个 true,说明通过上述三种方式获取的 Class 对象都是同一个,同一个字节码文件(*.class)在一次运行过程中只会被加载一次。


Class 对象的使用

获取成员变量

方法 说明

Field[] getFields() 返回包含一个数组 Field对象反射由此表示的类或接口的所有可访问的公共字段类对象

Field getField(String name) 返回一个 Field对象,它反映此表示的类或接口的指定公共成员字段类对象

Field[] getDeclaredFields() 返回的数组 Field对象反映此表示的类或接口声明的所有字段类对象

Field getDeclaredField(String name) 返回一个 Field对象,它反映此表示的类或接口的指定已声明字段类对象

Field[] getFields()


package com.cunyu;
import java.lang.reflect.Field;
/**
 * @author : cunyu
 * @version : 1.0
 * @className : Demo2
 * @date : 2021/4/7 23:39
 * @description : Class 对象的使用
 */
public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class class1 = Class.forName("com.cunyu.Person");
        Field[] fields = class1.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
    }
}

image.png回顾下我们的 Person 类,可以发现 idgrade 成员变量都是被 public 所修饰的,说明该方法是用于获取类中所有被 public 所修饰的成员变量(包括父类)。

  • Field getField(String name)
package com.cunyu;
import java.lang.reflect.Field;
/**
 * @author : cunyu
 * @version : 1.0
 * @className : Demo2
 * @date : 2021/4/7 23:39
 * @description : Class 对象的使用
 */
public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class class1 = Class.forName("com.cunyu.Person");
        Field field1 = class1.getField("id");
        System.out.println(field1);
        Field field2 = class1.getField("age");
        System.out.println(field2);
        Field field3 = class1.getField("rank");
        System.out.println(field3);
    }
}

image.png从上面的结果分析可知,该方法只能用于获取类中指定名称的 public 所修饰的成员变量,对于 protectedprivate 所修饰的成员变量,该方法是无法获取的(包括父类)。而获取或设置成员变量值时,可以通过 get/set 方法来操作,具体操作方法如下。

// 假设我们获取到的 Field 为上面的 id,获取和设置 id 的值就可以通过如下操作来进行
// 1. 获取
Field idField = personClass.getField("id");
Person person = new Person();
Object idValue = idField.get(person);
System.out.println("id:" + idValue);
// 2. 设置
idField.set(person, "1312120");
System.out.println("person:" + person);
  • Field[] getDeclaredFields()
package com.cunyu;
import java.lang.reflect.Field;
/**
 * @author : cunyu
 * @version : 1.0
 * @className : Demo2
 * @date : 2021/4/7 23:39
 * @description : Class 对象的使用
 */
public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class class1 = Class.forName("com.cunyu.Person");
        Field[] fields = class1.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field);
        }
    }
}

image.png观察上面的结果可知,该方法可用于获取所有的成员变量,不用考虑修饰符的限制(不包括父类)。

  • Field getDeclaredField(String name)
package com.cunyu;
import java.lang.reflect.Field;
/**
 * @author : cunyu
 * @version : 1.0
 * @className : Demo2
 * @date : 2021/4/7 23:39
 * @description : Class 对象的使用
 */
public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class class1 = Class.forName("com.cunyu.Person");
        Field field1 = class1.getDeclaredField("id");
        System.out.println(field1);
        Field field3 = class1.getDeclaredField("rank");
        System.out.println(field3);
        Field field2 = class1.getDeclaredField("age");
        System.out.println(field2);
    }
}

image.png观察上面的结果可知,该方法可用于获取指定的成员变量,不用考虑成员变量修饰符的限制(不包括父类)。但是在利用 set、get 方法来获取和设置 private、protected 修饰的成员变量时,需要利用 setAccessible() 来忽略访问全新啊修饰符的安全检查,否则程序将会报错。


获取构造方法

方法 说明

Constructor<?>[] getConstructors() 返回包含一个数组 Constructor对象反射由此表示的类的所有公共构造类对象

Constructor<T> getConstructor(类<?>... parameterTypes) 返回一个 Constructor 对象,该对象反映 Constructor对象表示的类的指定的公共类函数

Constructor<?>[] getDeclaredConstructors() 返回一个反映 Constructor 对象表示的类声明的所有 Constructor 对象的数组类

Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) 返回一个 Constructor 对象,该对象反映 Constructor 对象表示的类或接口的指定类函数


package com.cunyu;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
 * @author : cunyu
 * @version : 1.0
 * @className : Demo3
 * @date : 2021/4/8 13:28
 * @description : 构造对象获取
 */
public class Demo3 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class personClass = Class.forName("com.cunyu.Person");
//        1. 获取所有构造方法
        System.out.println("所有构造方法");
        Constructor[] constructors = personClass.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
//        2. 获取指定构造方法
//        空参构造方法
        System.out.println("空参构造方法");
        Constructor constructor1 = personClass.getConstructor();
        System.out.println(constructor1);
//        带参构造方法
        System.out.println("带参构造方法");
        Constructor constructor2 = personClass.getConstructor(int.class, String.class, long.class, long.class, float.class, int.class);
        System.out.println(constructor2);
//        获取构造方法后,可以利用它来创建对象
        System.out.println("空参创建对象");
//        第一种方法
        Object person = constructor1.newInstance();
        System.out.println(person);
//        第二种方法
        Object person1 = personClass.newInstance();
        System.out.println(person1);
        System.out.println("带参创建对象");
        Object object = constructor2.newInstance(20, "村雨遥", 1312020, 3, 99.0F, 2);
        System.out.println(object);
    }
}

image.pngConstructor<?>[] getConstructors()


类似于通过 Class 实例来获取成员变量,该方法用于获取所有 public 所修饰的构造方法(包括父类);


Constructor<T> getConstructor(类<?>... parameterTypes)


该方法用于获取某一指定参数类型后的 public 所修饰的构造方法(包括父类);


Constructor<?>[] getDeclaredConstructors()

该方法用于获取所有 public 所修饰的构造方法(不包括父类);


Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)

该方法用于获取某一指定参数类型后的 public 所修饰的构造方法(不包括父类);


而获取到构造方法之后,我们就可以利用 newInstance() 方法来创建类的实例。特殊的,如果我们的构造方法是无参的,此时则可以直接利用 Class.newInstance() 来构造实例。


获取成员方法

方法 说明

Method[] getMethods() 返回包含一个数组 方法对象反射由此表示的类或接口的所有公共方法 类对象,包括那些由类或接口和那些从超类和超接口继承的声明

Method getMethod(String name, 类<?>... parameterTypes) 返回一个方法对象,它反映此表示的类或接口的指定公共成员方法 类对象

Method[] getDeclaredMethods() 返回包含一个数组方法对象反射的类或接口的所有声明的方法,通过此表示 类对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法

Method getDeclaredMethod(String name, 类<?>... parameterTypes) 返回一个方法对象,它反映此表示的类或接口的指定声明的方法类对象


package com.cunyu;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
 * @author : cunyu
 * @version : 1.0
 * @className : Demo4
 * @date : 2021/4/8 13:51
 * @description : 成员方法获取
 */
public class Demo4 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class personClass = Class.forName("com.cunyu.Person");
//        获取所有 public 成员方法
        System.out.println("获取所有成员方法");
        Method[] methods = personClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
//        获取指定名称的方法
        System.out.println("获取指定名称的方法");
        Method getAgeMethod = personClass.getMethod("getAge");
        System.out.println(getAgeMethod);
//        执行方法
        Person person = new Person(20, "村雨遥", 1312020, 3, 99.0F, 2);
        int age = (int) getAgeMethod.invoke(person);
        System.out.println(age);
    }
}

image.pngMethod[] getMethods()

用于获取当前类的所有 public 所修饰的成员方法(包括父类)。


Method getMethod(String name, 类<?>... parameterTypes)

用于获取当前类的某一个指定名称 public 所修饰的成员方法(包括父类)。


Method[] getDeclaredMethods()

用于获取当前类的所有 public 所修饰的成员方法(不包括父类)。


Method getDeclaredMethods(String name, 类<?>... parameterTypes)

用于获取当前类的某一个指定名称 public 所修饰的成员方法(不包括父类)。


而当我们获取到类的成员方法后,如果要执行某一个方法,可以使用 invoke() 方法来执行该方法。


获取类名


package com.cunyu;
/**
 * @author : cunyu
 * @version : 1.0
 * @className : Demo5
 * @date : 2021/4/8 14:06
 * @description : 获取类名
 */
public class Demo5 {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Person();
        Class personClass = person.getClass();
        String className = personClass.getName();
        System.out.println(className);
    }
}

image.pngString getName()

从上述程序的结果可知,当我们获取到 Class 对象之后,如果不知道类的全名,就可以使用 getName() 来获取该类的全名。


反射实例

假设我们有如下需求:在不改变类的代码的前提下,我们能够创建任意类的对象,并执行其中的方法。


此时,我们可以通过 配置文件 + 反射 的方式来实现这一效果,而这也就是我们现在所用框架中的基础,当我们使用反射后,只需要通过修改配置文件中的内容就能够不用去改代码就实现对应的功能。


假设我们有两个类,一个 Student,一个 Teacher,两者的定义如下;


package com.cunyu;
/**
 * @author : cunyu
 * @version : 1.0
 * @className : Teacher
 * @date : 2021/4/8 15:15
 * @description : 教师类
 */
public class Teacher {
    private String name;
    private int age;
    public void teach() {
        System.out.println("教书育人……");
    }
}
package com.cunyu;
/**
 * @author : cunyu
 * @version : 1.0
 * @className : Student
 * @date : 2021/4/8 15:16
 * @description : 学生类
 */
public class Student {
    private String name;
    private float score;
    public void study() {
        System.out.println("好好学习,天天向上……");
    }
}

要实现我们的需求,通常需要如下步骤:


将要创建对象的全类名和要执行的方法都配置在配置文件中;

定义的配置文件 prop.properties ,其中主要内容包括 className 和 methodName 两个属性,分别代表类的全类名和要调用方法的名字。一个具体实例如下,分别代表名为 Student 的类和名为 study 的方法。


className=com.cunyu.Student

methodName=study


然后在主方法中加载读取配置文件;


//        创建配置文件对象
Properties properties = new Properties();
//        加载配置文件
ClassLoader classLoader = ReflectTest.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("prop.properties");
properties.load(inputStream);
//        获取配置文件中定义的数据
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");

利用反射技术将类加载到内存中;

//        加载进内存

Class name = Class.forName(className);


接着利用 newInstance() 方法创建对象;

//        创建实例

Object object = name.newInstance();


最后则是利用 invoke() 方法来执行方法;

//        获取并执行方法

Method method = name.getMethod(methodName);

method.invoke(object);


将整个流程汇总起来就是:


package com.cunyu;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
/**
 * @author : cunyu
 * @version : 1.0
 * @className : ReflectTest
 * @date : 2021/4/8 15:27
 * @description : 测试
 */
public class ReflectTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
//        创建配置文件对象
        Properties properties = new Properties();
//        加载配置文件
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        InputStream inputStream = classLoader.getResourceAsStream("prop.properties");
        properties.load(inputStream);
//        获取配置文件中定义的数据
        String className = properties.getProperty("className");
        String methodName = properties.getProperty("methodName");
//        加载进内存
        Class name = Class.forName(className);
//        创建实例
        Object object = name.newInstance();
//        获取并执行方法
        Method method = name.getMethod(methodName);
        method.invoke(object);
    }
}

此时,我们只需要改动配置文件 prop.properties 中的配置即可输出不同结果;

image.png

总结

好了,感谢耐心看到这里的各位。如果您觉得本文对您有所帮助,那就给我点个赞吧!

最后,对于文中知识点有错误或欠缺的地方,还请大家见谅,欢迎大家评论留言给我指正~

目录
相关文章
|
30天前
|
开发框架 Java API
java反射机制的原理与简单使用
java反射机制的原理与简单使用
17 1
|
11天前
|
Java Shell
Java 21颠覆传统:未命名类与实例Main方法的编码变革
Java 21颠覆传统:未命名类与实例Main方法的编码变革
13 0
|
18天前
|
安全 Java 调度
深入理解Java中的线程安全与锁机制
【4月更文挑战第6天】 在并发编程领域,Java语言提供了强大的线程支持和同步机制来确保多线程环境下的数据一致性和线程安全性。本文将深入探讨Java中线程安全的概念、常见的线程安全问题以及如何使用不同的锁机制来解决这些问题。我们将从基本的synchronized关键字开始,到显式锁(如ReentrantLock),再到读写锁(ReadWriteLock)的讨论,并结合实例代码来展示它们在实际开发中的应用。通过本文,读者不仅能够理解线程安全的重要性,还能掌握如何有效地在Java中应用各种锁机制以保障程序的稳定运行。
|
23天前
|
Java 程序员 开发者
深入理解Java异常处理机制
在Java编程中,异常处理是确保程序健壮性与稳定性的重要组成部分。本文旨在深度剖析Java异常处理机制的核心概念、结构及其实际应用策略,帮助开发者更好地理解并运用异常处理来优化程序设计。我们将从Java异常体系结构入手,探讨try-catch-finally语句块的执行流程,分析自定义异常的必要性与实现方式,并通过实例演示如何有效地管理和处理异常情况。
23 3
|
25天前
|
关系型数据库 Java 开发工具
Java入门高频考查基础知识9(15问万字参考答案)
本文探讨了Spring Cloud的工作原理,包括注册中心的心跳机制、服务发现机制,以及Eureka默认的负载均衡策略。同时,概述了Spring Boot中常用的注解及其实现方式,并深入讨论了Spring事务的注解、回滚条件、传播性和隔离级别。文章还介绍了MySQL的存储引擎及其区别,特别关注了InnoDB如何实现MySQL的事务处理。此外,本文还详细探讨了MySQL索引,包括B+树的原理和设计索引的方法。最后,比较了Git和SVN的区别,并介绍了Git命令的底层原理及流程。
32 0
Java入门高频考查基础知识9(15问万字参考答案)
|
25天前
|
存储 缓存 算法
Java入门高频考查基础知识4(字节跳动面试题18题2.5万字参考答案)
最重要的是保持自信和冷静。提前准备,并对自己的知识和经验有自信,这样您就能在面试中展现出最佳的表现。祝您面试顺利!Java 是一种广泛使用的面向对象编程语言,在软件开发领域有着重要的地位。Java 提供了丰富的库和强大的特性,适用于多种应用场景,包括企业应用、移动应用、嵌入式系统等。下是几个面试技巧:复习核心概念、熟悉常见问题、编码实践、项目经验准备、注意优缺点、积极参与互动、准备好问题问对方和知其所以然等,多准备最好轻松能举一反三。
49 0
Java入门高频考查基础知识4(字节跳动面试题18题2.5万字参考答案)
|
25天前
|
存储 算法 JavaScript
Java入门高频考查算法逻辑基础知识3-编程篇(超详细18题1.8万字参考编程实现)
解决这类问题时,建议采取下面的步骤: 理解数学原理:确保你懂得基本的数学公式和法则,这对于制定解决方案至关重要。 优化算法:了解时间复杂度和空间复杂度,并寻找优化的机会。特别注意避免不必要的重复计算。 代码实践:多编写实践代码,并确保你的代码是高效、清晰且稳健的。 错误检查和测试:要为你的代码编写测试案例,测试标准的、边缘情况以及异常输入。 进行复杂问题简化:面对复杂的问题时,先尝试简化问题,然后逐步分析和解决。 沟通和解释:在编写代码的时候清晰地沟通你的思路,不仅要写出正确的代码,还要能向面试官解释你的
33 0
|
25天前
|
存储 Java 编译器
Java入门高频考查基础知识2(超详细28题2.5万字答案)
多态是面向对象编程中的一个重要概念,它允许不同类的对象对同一消息作出不同的响应。在具体实现上,多态允许一个父类的引用指向其子类的对象,并根据实际指向的对象的类型来调用相应的方法。在 Java 中,多态可以通过以下几种方式实现:在同一个类中,方法名相同,但形参列表不同,实现了多态。子类可以重写(覆盖)其父类的方法,实现多态。在父类引用中调用该方法时,根据实际指向的子类对象的类型来调用相应的方法实现。
39 0
|
25天前
|
编解码 算法 安全
【Java技术专题】「入门到精通系列」深入探索Java技术中常用到的六种加密技术和实现
【Java技术专题】「入门到精通系列」深入探索Java技术中常用到的六种加密技术和实现
44 0
|
30天前
|
设计模式 XML 存储
java中的反射机制
java中的反射机制
12 1