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

总结

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

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

目录
相关文章
|
1月前
|
安全 Java 数据库连接
2025 年最新 Java 学习路线图含实操指南助你高效入门 Java 编程掌握核心技能
2025年最新Java学习路线图,涵盖基础环境搭建、核心特性(如密封类、虚拟线程)、模块化开发、响应式编程、主流框架(Spring Boot 3、Spring Security 6)、数据库操作(JPA + Hibernate 6)及微服务实战,助你掌握企业级开发技能。
240 3
|
2月前
|
人工智能 缓存 安全
Java中的反射机制:深入探索与应用
Java反射机制是程序运行时动态获取类信息并操作类成员的特性,具备高度灵活性,但也伴随性能与安全风险。本文详解反射的基本用法、高级应用及最佳实践,助你掌握这一强大工具的正确使用方式。
134 0
|
19天前
|
前端开发 Java 数据库连接
帮助新手快速上手的 JAVA 学习路线最详细版涵盖从入门到进阶的 JAVA 学习路线
本Java学习路线涵盖从基础语法、面向对象、异常处理到高级框架、微服务、JVM调优等内容,适合新手入门到进阶,助力掌握企业级开发技能,快速成为合格Java开发者。
254 3
|
1月前
|
NoSQL Java 关系型数据库
Java 从入门到进阶完整学习路线图规划与实战开发最佳实践指南
本文为Java开发者提供从入门到进阶的完整学习路线图,涵盖基础语法、面向对象、数据结构与算法、并发编程、JVM调优、主流框架(如Spring Boot)、数据库操作(MySQL、Redis)、微服务架构及云原生开发等内容,并结合实战案例与最佳实践,助力高效掌握Java核心技术。
211 2
|
1月前
|
Java 测试技术 API
Java IO流(二):文件操作与NIO入门
本文详解Java NIO与传统IO的区别与优势,涵盖Path、Files类、Channel、Buffer、Selector等核心概念,深入讲解文件操作、目录遍历、NIO实战及性能优化技巧,适合处理大文件与高并发场景,助力高效IO编程与面试准备。
|
1月前
|
Java 编译器 API
Java Lambda表达式与函数式编程入门
Lambda表达式是Java 8引入的重要特性,简化了函数式编程的实现方式。它通过简洁的语法替代传统的匿名内部类,使代码更清晰、易读。本文深入讲解Lambda表达式的基本语法、函数式接口、方法引用等核心概念,并结合集合操作、线程处理、事件回调等实战案例,帮助开发者掌握现代Java编程技巧。同时,还解析了面试中高频出现的相关问题,助你深入理解其原理与应用场景。
|
10天前
|
存储 Java 关系型数据库
Java 项目实战基于面向对象思想的汽车租赁系统开发实例 汽车租赁系统 Java 面向对象项目实战
本文介绍基于Java面向对象编程的汽车租赁系统技术方案与应用实例,涵盖系统功能需求分析、类设计、数据库设计及具体代码实现,帮助开发者掌握Java在实际项目中的应用。
31 0
|
10天前
|
Java API 数据库
2025 年最新 Java 实操学习路线,从入门到高级应用详细指南
2025年Java最新实操学习路线,涵盖从环境搭建到微服务、容器化部署的全流程实战内容,助你掌握Java 21核心特性、Spring Boot 3.2开发、云原生与微服务架构,提升企业级项目开发能力,适合从入门到高级应用的学习需求。
196 0
|
19天前
|
监控 Java API
2025 年全新出炉的 Java 学习路线:从入门起步到实操精通的详细指南
2025年Java学习路线与实操指南,涵盖Java 21核心特性、虚拟线程、Spring Boot 3、微服务、Spring Security、容器化部署等前沿技术,助你从入门到企业级开发进阶。
168 0
|
1月前
|
前端开发 Java 数据库
Java 项目实战从入门到精通 :Java Web 在线商城项目开发指南
本文介绍了一个基于Java Web的在线商城项目,涵盖技术方案与应用实例。项目采用Spring、Spring MVC和MyBatis框架,结合MySQL数据库,实现商品展示、购物车、用户注册登录等核心功能。通过Spring Boot快速搭建项目结构,使用JPA进行数据持久化,并通过Thymeleaf模板展示页面。项目结构清晰,适合Java Web初学者学习与拓展。
176 1