写点什么

深入剖析 Java 中的反射,由浅入深,层层剥离!

  • 2024-02-02
    福建
  • 本文字数:4042 字

    阅读完需:约 13 分钟

写在开头


之前更新了不少 Java 的基础知识,比如 Java 的类、对象、基础类型、关键字、序列化、泛型、值传递等等,今天要上点深度了,来聊一聊 Java 中的 反射 !


所谓反射,就是在运行时分析、检查和操作类、接口、方法、属性的行为!


简单感受一下反射


在开始详解反射之前,我们先通过一段代码,简单的感受一下反射的魅力!首先,我们已经写了一个 Person 类,类中有 age 和 name 属性,并提供了 set/get 方法。这时候分别通过正常的实例化对象调用,和反射调用去操作对象的属性值!


【代码示例 1】


	//Java正射实现	Person person = new Person();	person.setName("JavaBuild-正射");	System.out.println(person.getName());		//Java反射实现	//获取类的class对象	Class<?> classa = Class.forName("com.javabuild.server.pojo.Person");	//通过class获取类的元数据-setName/getName方法	Method setName = classa.getMethod("setName", String.class);	Method getName = classa.getMethod("getName");	//获取默认空构造方法	Constructor constructor = classa.getConstructor();	//通过newInstance()进行实例化	Object o = constructor.newInstance();	//通过invoke方法进行方法调用	setName.invoke(o,"JavaBuild-反射");	System.out.println(getName.invoke(o));
复制代码


以上为 Java 正射与反射的对比实现代码。从代码中我们可以看到,正常通过实例化对象后再调用相应方法的正射方式比通过反射的方式代码量要少很多,并且逻辑更加清晰明确。


使用场景


反射被称之为框架的灵魂,在 Java 中被大量使用在框架、动态代理、注解等场景下;


  • 开发框架:Spring、SpringBoot、Mybatis、Hibernate 等框架中使用了反射


  • 动态代理:在面向切面编程中,需要拦截特定的方法,就会选择动态代理的方式,而动态代理的底层技术就是反射。


  • 注解:注解本身只是起到一个标记符的作用,它需要利用反射机制,根据标记符去执行特定的行为。比如 @Component 注解就声明了一个类为 Spring Bean ,通过 @Value 注解读取配置文件中的值,这些的底层都是通过反射来实现需求。


实现过程


我们基于上述代码示例 1 中的反射进行分步分析,彻底搞明白反射的实现与使用!【步骤一】通过如下这句代码获取反射类的 Class 对象


Class<?> classa = Class.forName("com.javabuild.server.pojo.Person");
复制代码


可别小看这简简单单的一行代码,这里其实涉及到 3 个知识点,我们来扩展一下。


知识点一:什么是Class对象?这里提到的 Class 和之间了解的用来标识类的 class 关键字并不一样!Class 也是一个类,存放在 java.lang 包中,它的作用是:编译时生成一个类的 Class 对象,这个对象中包含了类的结构信息,如类名、继承父类、实现的接口、方法、属性等等,Class 对象保存在编译后的.class 文件中。

在我们 new 一个对象或者引用静态成员变量时,会将 Class 对象加载到 JVM 中,JVM 解释器去分析 Class 对象获取到类的结构信息,创建我们需要的实例对象或者提供静态成员变量的引用值。


因为类的 Class 对象时存储在 class 文件中,所以不管实例化多少个对象,都有且仅有一个 Class 对象!因为 Class 类对象本身的特性,所以它可以在运行时操作类,这对反射来说无疑是天赐良缘!


知识点二:获取Class对象的几种方式?


1、在已知具体类的情况下


Class person= Person.class;
复制代码


2、通过 Class.forName()传入类的全路径获取,代码示例 1 中采用该方式


Class classa = Class.forName("com.javabuild.server.pojo.Person");
复制代码


3、对象实例 instance.getClass()获取


Person person = new Person();Class aClass = person.getClass();
复制代码


4、通过类加载器 xxxClassLoader.loadClass()传入类路径获取


Class aClass = ClassLoader.getSystemClassLoader().loadClass("com.javabuild.server.pojo.Person");
复制代码


知识点三:java.lang.reflect包中常用的反射类


在 java.lang.reflect 包中存着几个反射常用的类,大概的罗列如下,注意,Class 类其实是放在 java.lang 中的。


Class:代表一个类或接口,包含了类的结构信息(如名称、构造函数、方法、字段等)。通过 Class 对象,可以获取类的元数据并操作类的实例。Constructor:代表类的构造方法,用于创建类的实例。Method:代表类的方法,可以通过它调用类的实例方法。Field:代表类的字段,可以获取或修改字段的值。Modifier:包含方法、字段和类的访问修饰符(如 public、private 等)。
复制代码


【步骤二】通过 Class 对象获取构造方法


Constructor constructor = classa.getConstructor();
复制代码


除了这种获取默认无参构造的方式,获取构造方法还可以通过如下这些方式:


getConstructor():返回反射类的特定 public 构造方法,可以传递参数,参数为构造方法参数对应 Class 对象;缺省的时候返回默认构造方法。getDeclaredConstructor():返回反射类的特定构造方法,不限定于 public 的。getConstructors():返回类的所有 public 构造方法。getDeclaredConstructors():返回类的所有构造方法,不限定于 public 的。
复制代码


【代码示例 2】


Class<?> classa = Class.forName("com.javabuild.server.pojo.Person");Constructor constructor01 = classa.getConstructor();System.out.println(constructor);System.out.println("---------------");Constructor<?>[] constructors = classa.getConstructors();for (Constructor<?> constructor02 : constructors) {    System.out.println(constructor02);}System.out.println("----------------");Constructor declaredConstructor = classa.getDeclaredConstructor(int.class);System.out.println(declaredConstructor);
复制代码


【控制台输出】


public com.javabuild.server.pojo.Person()---------------public com.javabuild.server.pojo.Person()public com.javabuild.server.pojo.Person(int)public com.javabuild.server.pojo.Person(int,java.lang.String)----------------public com.javabuild.server.pojo.Person(int)
复制代码


【步骤三】


通过 Constructor 对象初始化反射类对象


//通过newInstance()进行实例化Object o = constructor.newInstance();
复制代码


【步骤四】


获取要调用的方法的 Method 对象


//通过class获取类的元数据-setName/getName方法Method setName = classa.getMethod("setName", String.class);Method getName = classa.getMethod("getName");
复制代码


【步骤五】


通过 invoke 方法进行方法调用


setName.invoke(o,"JavaBuild-反射");
复制代码


以上即为反射使用的全过程!下面贴一下全量代码。


Person类


public class Person {    private int age;    private String name;
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 Person() { } //一个参数构造 public Person(int age) { this.age = age; } //2个参数构造 public Person(int age, String name) { this.age = age; this.name = name; }}
复制代码


反射测试类


public class Test {    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {        //Java正射实现        Person person = new Person();        person.setName("JavaBuild-正射");        System.out.println(person.getName());
//Java反射实现 //获取类的class对象 Class<?> classa = Class.forName("com.javabuild.server.pojo.Person"); //通过class获取类的元数据-setName/getName方法 Method setName = classa.getMethod("setName", String.class); Method getName = classa.getMethod("getName"); //获取默认空构造方法 Constructor constructor = classa.getConstructor(); //通过newInstance()进行实例化 Object o = constructor.newInstance(); //通过invoke方法进行方法调用 setName.invoke(o,"JavaBuild-反射"); System.out.println(getName.invoke(o));
//获取默认空构造方法 Constructor constructor01 = classa.getConstructor(); System.out.println(constructor); System.out.println("---------------"); Constructor<?>[] constructors = classa.getConstructors(); for (Constructor<?> constructor02 : constructors) { System.out.println(constructor02); } System.out.println("----------------"); Constructor declaredConstructor = classa.getDeclaredConstructor(int.class); System.out.println(declaredConstructor); }}
复制代码


输出


JavaBuild-正射JavaBuild-反射public com.javabuild.server.pojo.Person()---------------public com.javabuild.server.pojo.Person()public com.javabuild.server.pojo.Person(int)public com.javabuild.server.pojo.Person(int,java.lang.String)----------------public com.javabuild.server.pojo.Person(int)
复制代码


优缺点


基于上面的内容,我们可以对 java 中的反射做一个总结吧


优点

1、可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利。


缺点


1、破坏封装:由于反射允许访问私有字段和私有方法,所以可能会破坏封装而导致安全问题。


2、性能开销:由于反射涉及到动态解析,因此无法执行 Java 虚拟机优化,再加上反射的写法的确要复杂得多,所以性能要比“正射”差很多,在一些性能敏感的程序中应该避免使用反射。


文章转载自:JavaBuild

原文链接:https://www.cnblogs.com/JavaBuild/p/18002022

体验地址:http://www.jnpfsoft.com/?from=001

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
深入剖析Java中的反射,由浅入深,层层剥离!_Java_不在线第一只蜗牛_InfoQ写作社区