Java --- 反射

目录

一.什么是反射?

二.反射的核心方法和功能: 

1.获取类的元信息:

2. 动态实例化对象:

3. 访问字段(包括私有字段):

4. 调用方法(包括私有方法):

5.动态代理:


一.什么是反射?

Java 反射(Reflection)是 Java 提供的一种功能,可以在运行时检查类、接口、字段和方法的信息,同时还可以动态调用对象的方法或构造器,甚至修改字段的值。反射使程序具有很强的动态性,但也可能带来一定的性能和安全问题。

反射的核心是 java.lang.reflect 包,其中包含了如下主要类:

  • Class:提供了类的元信息。
  • Field:表示类的字段。
  • Method:表示类的方法。
  • Constructor:表示类的构造方法。

class(包括interface)的本质是数据类型(Type)。无继承关系的数据类型无法赋值:

Number n = new Double(123.456); // OK
String s = new Double(123.456); // compile error!

class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。它长这样:

public final class Class {private Class() {}
}

所以,JVM持有的每个Class实例都指向一个数据类型(classinterface),并且一个Class实例包含了该class的所有完整信息。

由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等。因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。而通过Class实例获取class信息的方法称为反射(Reflection)

二.反射的核心方法和功能: 

反射的核心方法和功能如下:

  1. 获取类的元信息
  2. 动态实例化对象
  3. 访问字段(包括私有字段)
  4. 调用方法(包括私有方法)

1.获取类的元信息:

通过反射获取类的包名、类名和方法等信息。

import java.lang.reflect.Method;public class ReflectionExample {public static void main(String[] args) {try {// 获取 Class 对象:// 1. 使用 Class.forName()Class<?> clazz = Class.forName("java.util.ArrayList");// 2. 使用 类名.classClass<?> clazz2 = ArrayList.class;// 3. 使用对象.getClass()ArrayList<String> list = new ArrayList<>();Class<?> clazz3 = list.getClass();System.out.println(clazz1 == clazz2); // trueSystem.out.println(clazz2 == clazz3); // true// 打印类名和包名System.out.println("类名: " + clazz.getName());System.out.println("简单类名: " + clazz.getSimpleName());System.out.println("包名: " + clazz.getPackage().getName());// 获取所有方法Method[] methods = clazz.getMethods();System.out.println("类中的方法:");for (Method method : methods) {System.out.println(method.getName());}} catch (ClassNotFoundException e) {e.printStackTrace();}}
}
输出示例:
类名: java.util.ArrayList
简单类名: ArrayList
包名: java.util
类中的方法:
add
remove
size
clear
...
  • Class.forName:通过类的完全限定名动态加载类。
  • getMethods:返回类及其父类的所有公共方法。

注意一下Class实例比较和instanceof的差别:

Integer n = new Integer(123);boolean b1 = n instanceof Integer; // true,因为n是Integer类型
boolean b2 = n instanceof Number; // true,因为n是Number类型的子类boolean b3 = n.getClass() == Integer.class; // true,因为n.getClass()返回Integer.class
Class c1 = n.getClass();
Class c2 = Number.class;
boolean b4 = c1 == c2; // false,因为Integer.class != Number.class

instanceof不但匹配指定类型,还匹配指定类型的子类。而==判断class实例可以精确地判断数据类型,但不能作子类型比较

而通常情况下,我们应该用instanceof判断数据类型,因为在面向抽象编程的时候,我们不关心具体的子类型。只有在需要精确判断一个类型是不是某个class的时候,我们才使用==判断class实例。

注意:数组(例如String[])也是一种类,而且不同于String.class,它的类名是[Ljava.lang.String;。此外,JVM为每一种基本类型如int也创建了Class实例,通过int.class访问。 

因为反射的目的是为了获得某个实例的信息。因此,当我们拿到某个Object实例时,我们就可以通过反射获取该Objectclass信息:

void printObjectInfo(Object obj) {Class clazz = obj.getClass();
}

2. 动态实例化对象:

通过反射动态创建类的实例。

假如我们获取到了一个Class实例,我们就可以通过该Class实例来创建对应类型的实例: 

// 获取String的Class实例:
Class cls = String.class;
// 创建一个String实例:
String s = (String) cls.newInstance();

上述代码相当于new String()。通过Class.newInstance()可以创建类实例,它的局限是:只能调用public的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。

为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且调用结果总是返回实例: 

import java.lang.reflect.Constructor;public class DynamicInstanceExample {public static void main(String[] args) {try {// 获取 Class 对象Class<?> clazz = Class.forName("java.lang.String");// 获取构造方法(使用 getConstructor 获取特定参数的构造器)Constructor<?> constructor = clazz.getConstructor(String.class);// 创建对象实例(通过 newInstance 动态创建对象)Object instance = constructor.newInstance("Hello Reflection");System.out.println("实例内容: " + instance); // 实例内容: Hello Reflection// 获取构造方法Integer(int):Constructor cons1 = Integer.class.getConstructor(int.class);// 调用构造方法:Integer n1 = (Integer) cons1.newInstance(123);System.out.println(n1); // 123// 获取构造方法Integer(String)Constructor cons2 = Integer.class.getConstructor(String.class);Integer n2 = (Integer) cons2.newInstance("456");System.out.println(n2); // 456} catch (Exception e) {e.printStackTrace();}}
}

3. 访问字段(包括私有字段):

对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。而在Java中,Field类是Java反射机制的一部分,用于表示类的成员变量。通过Field对象,我们可以获取类的字段(成员变量)的信息,并且能够在运行时动态地访问或修改这些字段的值。

要获取类的字段(成员变量),需要通过Class对象获取对应的Field对象。可以通过以下两种方式:

  1. 获取类的所有字段:使用 getDeclaredFields()方法可以获取类中所有的字段,包括私有字段(private)和公共字段(public)。

  2. 获取某个特定字段:使用 getDeclaredField(String name)方法获取指定名称的字段。

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类) 
import java.lang.reflect.Field;class Person {private String name = "John";
}public class AccessPrivateFieldExample {public static void main(String[] args) {try {Person person = new Person();Class<?> clazz = person.getClass();// 获取类的私有字段Field field = clazz.getDeclaredField("name");field.setAccessible(true); // 取消 Java 的访问权限检查// 获取和修改字段值System.out.println("原始值: " + field.get(person));field.set(person, "Alice");System.out.println("修改后的值: " + field.get(person));} catch (Exception e) {e.printStackTrace();}}
}

 下面是使用field对不同权限属性的访问:

// reflection
public class Main {public static void main(String[] args) throws Exception {Class<?> stdClass = Student.class;// 获取public字段"score":System.out.println(stdClass.getField("score"));// 获取继承的public字段"name":System.out.println(stdClass.getField("name"));// 获取private字段"grade":System.out.println(stdClass.getDeclaredField("grade"));}
}class Student extends Person {public int score;private int grade;
}class Person {public String name;
}

还有其他的常见方法:

  • getName():获取字段的名称。
  • getType():获取字段的类型(返回Class对象)。
  • getModifiers():获取字段的修饰符(如publicprivate等)。
  • set(Object obj, Object value):设置字段值。
  • get(Object obj):获取字段值。

Field类使得Java反射具有非常强的动态性,允许我们在程序运行时进行更细粒度的操作。不过,过多使用反射可能会影响性能,并增加代码的复杂性,因此建议在需要动态行为或框架开发时使用。

4. 调用方法(包括私有方法):

我们已经能通过Class实例获取所有Field对象,同样我们也可以通过Class实例获取所有Method信息。 

Class类提供了以下几个方法来获取Method

  • Method getMethod(name, Class...):获取某个publicMethod(包括父类)
  • Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有publicMethod(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
// reflection
public class Main {public static void main(String[] args) throws Exception {Class stdClass = Student.class;// 获取public方法getScore,参数为String:System.out.println(stdClass.getMethod("getScore", String.class));// 获取继承的public方法getName,无参数:System.out.println(stdClass.getMethod("getName"));// 获取private方法getGrade,参数为int:System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));}
}class Student extends Person {public int getScore(String type) {return 99;}private int getGrade(int year) {return 1;}
}class Person {public String getName() {return "Person";}
}

一个Method对象包含一个方法的所有信息:

  • getName():返回方法名称,例如:"getScore"
  • getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class
  • getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
  • getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。

我们正常的(直接的)方法调用指的是通过对象直接调用类中的方法。例如:

String s = "Hello world";
String result = s.substring(6);  // 直接调用substring方法
System.out.println(result);  // 输出 "world"

反射调用则是通过Java反射机制来动态地调用对象的方法。通过Method.invoke(),可以在运行时决定调用哪个方法,并传递参数。例如:

import java.lang.reflect.Method;public class Main {public static void main(String[] args) throws Exception {String s = "Hello world";Method m = String.class.getMethod("substring", int.class);  // 获取substring方法String result = (String) m.invoke(s, 6);  // 动态调用substring方法System.out.println(result);  // 输出 "world"}
}

invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。 

反射调用特点:

  • 使用invoke时,方法的调用是动态的,在运行时解析方法。你需要提供Method对象来引用类中的方法。
  • 可以调用私有方法或无法直接访问的非公共方法(通过setAccessible(true)来访问)。
  • 反射调用的参数和返回值是通过Object类型来传递和返回的,因此需要进行类型转换。
  • 反射方法的调用通常比直接调用慢,因为它需要通过反射机制来查找和执行方法。
  • 能动态地加载和调用方法,使得代码更具灵活性(例如插件系统、动态执行等)。

invoke方法比正常的直接方法调用灵活,但代价是性能上有所牺牲,并且不能享受编译时的类型检查。

特性正常方法调用反射调用 invoke
性能较高,直接编译时检查方法较低,涉及反射和运行时解析
编译时检查直接通过类的方法签名进行检查不在编译时检查,运行时才解析
灵活性方法在编译时已固定,不能动态选择方法可以动态选择方法,适用于动态行为
方法调用直接调用方法,参数明确需要通过反射获取Method对象,并传递参数
访问控制只能访问公共方法可以通过setAccessible(true)访问私有方法

eg: 

import java.lang.reflect.Method;class Calculator {private int add(int a, int b) {return a + b;}
}public class InvokePrivateMethodExample {public static void main(String[] args) {try {Calculator calculator = new Calculator();Class<?> clazz = calculator.getClass();// 获取私有方法Method method = clazz.getDeclaredMethod("add", int.class, int.class);method.setAccessible(true);// 调用私有方法Object result = method.invoke(calculator, 10, 20);System.out.println("结果: " + result);} catch (Exception e) {e.printStackTrace();}}
}

5.动态代理:

Java的动态代理(Dynamic Proxy)是指在程序运行时,动态生成代理类并创建代理对象的技术。通过动态代理,可以在不修改目标类的情况下,增加额外的功能或逻辑(如日志记录、性能监控、权限控制等)。Java的动态代理有两种常见的实现方式:JDK动态代理CGLIB动态代理。在这里,下面我们将重点讲解JDK动态代理,并涉及CGLIB的相关内容。

(1)动态代理的思想:

通过反射机制,在运行时创建一个代理类,该类实现目标接口,并且代理类内部将调用实际目标对象的方法。在调用过程中,代理类可以执行额外的操作(如日志、事务等)再调用目标方法。

(2)动态代理的好处:

  • 无需修改目标类:代理类不需要继承或实现目标类,只需要实现目标类的接口。
  • 灵活性高:代理行为可以在运行时动态生成和调整。
  • 增强功能:可以在不改变原有代码的情况下为目标方法提供额外的功能。

那么如何不编写实现类,直接在运行期创建某个interface的实例呢?

动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。

我们仍然先定义了接口Hello,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class Main {public static void main(String[] args) {// InvocationHandler 是一个接口,它必须实现 invoke() 方法。这个方法在代理实例的方法被调用时执行。InvocationHandler handler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(method);if (method.getName().equals("morning")) {System.out.println("Good morning, " + args[0]);}return null;}};// 创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码Hello hello = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(), // 传入用于加载代理类的ClassLoader,通常可以使用接口的getClassLoader()来获取。new Class[] { Hello.class }, // 传入需要实现的接口数组handler // 传入处理调用方法的InvocationHandler,包含具体的业务逻辑); // 调用代理对象hello的morning方法hello.morning("Bob");}
}interface Hello {void morning(String name);
}

invoke() 方法接收三个参数:

  • proxy: 被代理的对象(即代理类实例)。
  • method: 被调用的方法对象,包含了方法的名称、参数类型等信息。
  • args: 被调用方法的参数。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/35410.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

星海智算:skl-videolingo-2.0(AI视频翻译)使用教程

&#xff08;一&#xff09;项目介绍 VideoLingo是一款专为视频创作者设计的开源自动化工具&#xff0c;旨在提供从视频字幕生成到声音克隆配音的一站式服务。以下是对VideoLingo的详细介绍&#xff1a; 1、核心功能​ 1.1、一键全自动视频搬运​ 支持从YouTube等平台下载视…

SQL靶场第八关攻略

一.判断类型 输入?id1 and 11-- 输入?id1 and 12--页面都正常&#xff0c;说明不是数值型 输入?id1页面没有回显 加上--页面正常&#xff0c;说明是字符型注入 二.判断列数 输入?id1 order by 3--页面正常 输入?id1 order by 4--页面没有回显&#xff0c;说明一共有三列…

华为HCIP-Datacom H12-821H12-831 (12月最新题库)

备考HCIP-datacom的小伙伴注意啦 !!! 2024年下半年12月份最新(H12-821和H12-831)题库带解析,有需要的小伙伴移动至文章末 H12-821: H12-831: 1.BGP 邻居建立过程的状态存在以下几种&#xff1a;那么建立一个成功的连接所经历的状态机顺序是 A、3-1-2-5-4 B、1-3-5-2-4 C、…

Flask使用长连接

Flask使用flask_socketio实现websocket Python中的单例模式 在HTTP通信中&#xff0c;连接复用&#xff08;Connection Reuse&#xff09;是一个重要的概念&#xff0c;它允许客户端和服务器在同一个TCP连接上发送和接收多个HTTP请求/响应&#xff0c;而不是为每个新的请求/响…

MR30分布式 I/O 模块助力 CNC 设备产能飞跃

背景分析 在现代制造业中&#xff0c;CNC 设备扮演着极为关键的角色。然而&#xff0c;CNC 设备在运行过程中也存在着诸多痛点。传统的 CNC 设备往往在控制与通信方面存在局限&#xff0c;其内部的 I/O 系统大多采用集中式架构。这种架构下&#xff0c;一旦需要处理大量的输入输…

远程修改ESXi 6.7管理IP地址

1.启用安全Shell&#xff08;也就是EXSi可以被SSH访问的功能&#xff09; 2.使用SecureCRT SSH2连接ESXi主机&#xff0c;现在使用dcui并没有任何反应&#xff0c;在Session标签栏右键点击Disconnect。 The time and date of this login have been sent to the system logs.WA…

Vulnhub靶场 Kioptrix: Level 1 (#1) 练习

目录 0x00 环境准备0x01 主机信息收集0x02 站点信息收集0x03 漏洞查找与利用1. 方法一&#xff1a;mod_ssl 2.8.42. 方法二&#xff1a;CVE-2003-02013. 方法三&#xff1a;Samba 0x04 总结 0x00 环境准备 下载链接&#xff1a;http://www.kioptrix.com/dlvm/Kioptrix_Level_1.…

消息中间件-Kafka3-kafkaJavaClient小例

消息中间件-Kafka3-kafkaJavaClient小例 Kafak Java Client private static final String KAFKA_TOPIC "kafak-test";private static String bootstrapServers "localhost:9092";private static AdminClient client null;static {Properties config n…

关于光耦合器的常见误解

光耦合器以其提供电气隔离的能力而闻名&#xff0c;广泛应用于从电源到通信系统的各种应用。尽管光耦合器非常普遍&#xff0c;但人们对其特性和用途存在一些常见的误解。本文将揭穿一些最常见的误解&#xff0c;以帮助工程师和爱好者做出更明智的决策。 误解1&#xff1a;光耦…

【简洁明快】使用python读取数据建立pptx (python-pptx图文调整案例)

使用python自动读取数据建立pptx 前言如何使用 Python 自动生成 PPTX第一步&#xff1a;安装所需库第二步&#xff1a;创建一个新的 PPTX第三步&#xff1a;添加幻灯片第四步&#xff1a;添加内容添加文本添加图片第五步&#xff1a;保存 PPTX 图文实操案例&#xff08;自动读取…

【智体OS】官方上新发布rtphone分布式安卓设备远程控制插件:实现远程访问和管理手机

【智体OS】官方上新发布rtphone分布式安卓设备远程控制插件&#xff1a;实现远程访问和管理手机 dtns.network是一款主要由JavaScript编写的智体世界引擎&#xff08;内嵌了three.js编辑器的定制版-支持以第一视角浏览3D场馆&#xff09;&#xff0c;可以在浏览器和node.js、d…

Vue智慧商城项目

创建项目 vue组件库 — vant-ui&#xff08;常用于移动端&#xff09; Vant 2 - 轻量、可靠的移动端组件库 安装vant npm i vantlatest-v2 -S 引入组件 按需导入和全部导入 全部导入 整个组件库的所有组件都导进来&#xff0c;缺点是增加了代码包体积 main.js import…

提升网站流量的关键:AI在SEO关键词优化中的应用

内容概要 在当今数字时代&#xff0c;提升网站流量已成为每个网站管理员的首要任务。而人工智能的技术进步&#xff0c;为搜索引擎优化&#xff08;SEO&#xff09;提供了强有力的支持&#xff0c;尤其是在关键词优化方面。关键词是连接用户需求与网站内容的桥梁&#xff0c;其…

以MP6924A为核心的LLC拓扑学习【一】

PFCLLC: 在PFC&#xff08;功率因数校正&#xff09;和LLC&#xff08;谐振变换器&#xff09;组成的电源系统中&#xff0c;各个电路有特定的作用&#xff0c;它们协同工作以实现高效率和高功率因数的电能转换。 1. PFC&#xff08;功率因数校正&#xff09;电路的作用 PFC电…

实践教程|Transformer Decoder-Only 模型批量生成 Trick

导读 本文给出了一个用单Transformer decoder&#xff08; GPT&#xff09;模型进行批量生成时的解决方法。 发现用单 Transformer decoder &#xff08;Aka GPT&#xff09;模型进行生成时&#xff0c;因为位置对齐等问题&#xff0c;进行批量生成时十分麻烦。 训练时&#…

DevExpress WPF v24.2新功能预览 - 键盘导航和屏幕阅读器功能增强

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

threejs相机辅助对象cameraHelper

为指定相机创建一个辅助对象&#xff0c;显示这个相机的视锥。 想要在场景里面显示相机的视锥&#xff0c;需要创建两个相机。 举个例子&#xff0c;场景中有个相机A&#xff0c;想要显示相机A的视锥&#xff0c;那么需要一个相机B&#xff0c;把B放在A的后面&#xff0c;两个…

财务规划的变革:如何推动数据科学的转型和分析

在快速发展的金融世界中&#xff0c;财务专业人士越来越需要超越传统预算方式的数据分析方法&#xff0c;将现代化的预算技术、工具和方法引入到我们的企业发展过程中&#xff0c;并在企业内部发挥更具战略性的作用。数据科学、财务预测和预算分析是企业财务领域成功所必需的核…

PyTorch环境迁移指南

在进行深度学习研究和开发时,我们经常需要在不同计算机之间迁移PyTorch环境。无论是更换新设备还是在多台机器间协同工作,都需要确保环境配置的一致性。本文将详细介绍PyTorch环境迁移的完整流程和注意事项。 环境迁移看似简单,实则暗藏玄机。直接复制文件可能会遇到系统差异带…

深信服ATRUST与锐捷交换机端口链路聚合的配置

深信服ATRUST业务口原来只配置使用一个电口&#xff0c;近期出现流量达到800-900M接近端口的极限带宽。由于设备没有万光口&#xff0c;于是只好用2个光口来配置链接聚合。 下需附上深信服ATRST端口配置的截图&#xff0c;由于深信服ATRUST与锐捷交换机端口只共同支持源mac目的…