当前位置: 首页 > news >正文

【golang/go语言】Go语言之反射

本文参考了李文周的博客——Go语言基础之反射。

一、反射初识

1. 什么是反射

在计算机科学中,反射是指计算机程序在运行时(run time)可以访问、检测和修改它本身状态和行为的一种能力。用比喻来说,反射就是程序在运行的时候能够观察并修改自己的行为。

2. 使用场景

  • 编写函数时,并不知道调用者传入的参数类型是什么,可能是没约定好,也可能是传入的类型很多,这些类型不能统一表示
  • 有时候需要根据某些条件调用哪个函数,此时需要对函数和函数的参数进行反射,在运行期间动态的执行函数

3. 缺点

  • 反射相关的代码难以阅读
  • Go语言作为一门静态语言,在编码过程中可以发现一些类型错误,但对于反射代码则是无能为力的
  • 反射对性能影响比较大,比正常代码运行速度慢一到两个数量级

二、reflect包

Go语言中的接口值都具有具体类型和具体类型的值两部分信息,而类型(Type)和值(Value)可以理解为定义在reflect包下的两个结构体reflect.Type和reflect.Value。

在这里插入图片描述

reflect包下有两个最基础的函数——TypeOf和ValueOf,分别用于获取对象的类型信息和值信息,也即reflect.Type和reflect.Value对象。

函数说明
func TypeOf(i any) Type获取对象的类型信息
func ValueOf(i any) Value获取对象的值信息

1. refelect.TypeOf函数

(1) TypeOf函数

通过relect.TypeOf()函数我们可以获取任意对象的类型信息,即reflect.Type对象。

package mainimport ("fmt""reflect"
)func main() {var x float64 = 3.4fmt.Println("type:",reflect.TypeOf(x)) //type: float64
}

(2) Type和Kind

反射中的类型可以分为类型(Type)和种类(Kind)两种。类型(Type)是在变量声明时被赋予的类型(静态类型)或者运行时的类型(动态类型,又称具体类型)。而种类(Kind)是指数据底层的类型,种类是有限可枚举的。reflect包下定义的Kind类型包含以下几种:

type Kind uint
const (Invalid Kind = iota  // 非法类型Bool                 // 布尔型Int                  // 有符号整型Int8                 // 有符号8位整型Int16                // 有符号16位整型Int32                // 有符号32位整型Int64                // 有符号64位整型Uint                 // 无符号整型Uint8                // 无符号8位整型Uint16               // 无符号16位整型Uint32               // 无符号32位整型Uint64               // 无符号64位整型Uintptr              // 指针Float32              // 单精度浮点数Float64              // 双精度浮点数Complex64            // 64位复数类型Complex128           // 128位复数类型Array                // 数组Chan                 // 通道Func                 // 函数Interface            // 接口Map                  // 映射Ptr                  // 指针Slice                // 切片String               // 字符串Struct               // 结构体UnsafePointer        // 底层指针
)

反射对象的Kind可以通过reflect.Value或reflect.Type类型对象的Kind()函数获取。reflect.Value或reflect.Type类型对象有很多同名函数,它们有些功能相同,有些在返回值有些许区别,本文后面对此有更多的例子。

2. reflect.ValueOf函数

(1) ValueOf函数

通过reflect.ValueOf()函数我们可以获取任意对象的值信息,即reflect.Value对象,其中包含了原始值的值信息。

package mainimport ("fmt""reflect"
)func main() {var x float64 = 3.4fmt.Println("value:",reflect.ValueOf(x)) //value: 3.4
}

(2) Value转为原始值

除了可以通过ValueOf()函数将原始值转为reflect.Value对象外,还可以将reflect.Value对象转为原始值,reflect.Value类型提供的获取原始值的方法如下:

方法说明
func (v Value) Interface() interface {}将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
func (v Value) Int() int64将值以 int 类型返回,所有有符号整型均可以此方式返回
func (v Value) Uint() uint64将值以 uint 类型返回,所有无符号整型均可以此方式返回
func (v Value) Float() float64将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
func (v Value) Bool() bool将值以 bool 类型返回
func (v Value) Bytes() []bytes将值以字节数组 []bytes 类型返回
func (v Value) String() string将值以字符串类型返回

可以发现通过Interface方法获取原始值的方式是最为通用的一种。

3. isNil和isValid方法

由于函数参数传递是值拷贝,所以必须传递变量的地址才能修改变量的值。在反射中可以使用Elem()方法来获取指针对应的值。

(1) isNil方法

func (v Value) IsNil() bool

IsNil()报告v持有的值是否为nil,IsNil()常被用于判断指针是否为空。。v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic。

(2) isValid方法

func (v Value) IsValid() bool

IsValid()返回v是否持有一个值,IsValid()常被用于判定返回值是否有效。

(3) 代码示例

func main() {// *int类型空指针var a *intfmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil())// nil值fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid())// 实例化一个匿名结构体b := struct{}{}// 尝试从结构体中查找"abc"字段fmt.Println("不存在的结构体成员:", reflect.ValueOf(b).FieldByName("abc").IsValid())// 尝试从结构体中查找"abc"方法fmt.Println("不存在的结构体方法:", reflect.ValueOf(b).MethodByName("abc").IsValid())// mapc := map[string]int{}// 尝试从map中查找一个不存在的键fmt.Println("map中不存在的键:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("娜扎")).IsValid())
}

三、结构体反射

如果反射对象的类型是结构体,反射类型reflect.Type和反射值reflect.Value对象提供了对应的方法,来获取结构体的字段信息和方法信息,并且可以通过反射来调用结构体的方法。

1. 与结构体字段、方法相关的方法

下表中所列举的方法,除了Call()函数之外,其他的都是reflect.Value和reflect.Type都有的,只不过reflect.Value的同名函数返回结果的类型都是reflect.Value对象。

方法说明
func (t Type) Field(i int) StructField根据索引,返回索引对应的结构体字段的信息。
func (t Type) NumField() int返回结构体成员字段数量。
func (t Type) FieldByName(name string) (StructField, bool)根据给定字符串返回字符串对应的结构体字段的信息。
func (t Type) FieldByIndex(index []int) StructField多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。
func (t Type) FieldByNameFunc(match func(string) bool) (StructField,bool)根据传入的匹配函数匹配需要的字段。
func (t Type) NumMethod() int返回该类型的方法集中方法的数目
func (t Type) Method(int) Method返回该类型方法集中的第i个方法
func (t Type) MethodByName(string) (Method, bool)根据方法名返回该类型方法集中的方法
func (v Value) Call([]Value) []Value根据传参调用结构体的方法

2. 获取结构体字段信息

(1) StructField类型

在上表中,有些方法的返回值类型为StructField类型,它是用来描述结构体的一个字段信息的,其定义如下:

type StructField struct {Name    string      // 字段的名字PkgPath string      // 非导出字段的包路径,对于导出字段该字段值为""Type      Type      // 字段的类型Tag       StructTag // 字段的标签Offset    uintptr   // 字段在结构体中的字节偏移量Index     []int     // 用于Type.FieldByIndex时的索引Anonymous bool      // 是否匿名字段
}

(2) 获取结构体字段信息步骤

获取结构体字段信息可以分为以下几步:

  • 先获取interface的reflect.Type,然后通过NumField进行遍历
  • 再通过reflect.Type的Field方法根据下标获取其Field
  • 最后通过reflect.Value的Field的Interface()得到对应的value

当然也可以使用FieldByName方法根据字段名获取字段信息。

2. 获取结构体方法信息

(1) Method类型

在上表中,有些方法的返回值类型为Method类型,它是用来描述结构体的一个方法信息的,其定义如下:

type Method struct {Name string    // 方法名称PkgPath string // 非导出方法的包路径,对于导出方法该字段值为""Type  Type     // 方法类型Func  Value    // 带有接收器作为第一个参数的方法Index int      // 用于Type.MethodByIndex时的索引
}

(2) 获取结构体方法信息步骤

获取结构体方法信息可以分为以下几步:

  • 先获取interface的reflect.Type,然后通过NumMethod进行遍历
  • 再通过reflect.Type的Method根据下标获取其Method
  • 使用reflect.Value的Call函数调用结构体的Method

当然也可以使用MethodByName方法根据方法名获取方法信息。

3. 代码示例

package mainimport ("fmt""reflect"
)// 定义一个结构体
type Person struct {Name string `json:"name"`Age  int    `json:"age"`Sex  string `json:"sex"`
}// 注意,如果是指针 *Person 类型,则不算做是 Person 的Method
func (p Person) PrintInfo() {fmt.Printf("name:%s, age:%d, sex:%s\n", p.Name, p.Age, p.Sex)
}func (p Person) Say(msg string) {fmt.Println("hello,", msg)
}func main() {p := Person{Name: "zuzhiang",Age:  27,Sex:  "female",}getFieldAndMethod(p)
}// getFieldAndMethod 通过接口来获取任意参数,并打印结构体的字段和方法信息
func getFieldAndMethod(input interface{}) {getType := reflect.TypeOf(input)           // 获取input的类型fmt.Println("Type.Name: ", getType.Name()) // Personfmt.Println("Type.Kind: ", getType.Kind()) // structgetValue := reflect.ValueOf(input) // 获取input的值fmt.Println("Value:", getValue) // {zuzhiang 27 female}fmt.Println("----------------------------------------\n")// 获取结构体字段// 1. 先获取interface的reflect.Type,然后通过NumField进行遍历// 2. 再通过reflect.Type的Field方法根据下标获取其Field// 3. 最后通过reflect.Value的Field的Interface()得到对应的valuefor i := 0; i < getType.NumField(); i++ {field := getType.Field(i)value := getValue.Field(i).Interface() //获取第i个值fmt.Printf("字段名: %s, 字段类型: %s, 字段索引: %d, json tag: %s, 字段值: %v \n",field.Name, field.Type, field.Index, field.Tag.Get("json"), value)}fmt.Println("----------------------------------------\n")// 定义函数调用时的参数,Call函数的参数类型必须是[]reflect.Valuemsg := "zuzhiang"paramList := make([]reflect.Value, 0)paramList = append(paramList, reflect.ValueOf(msg))args := [][]reflect.Value{nil,paramList,}// 通过反射,操作方法// 1. 先获取interface的reflect.Type,然后通过NumMethod进行遍历// 2. 再通过reflect.Type的Method根据下标获取其Method// 3. 使用reflect.Value的Call函数调用结构体的Methodfor i := 0; i < getType.NumMethod(); i++ {method := getType.Method(i)fmt.Printf("方法名称: %s, 方法类型: %v \n", method.Name, method.Type)// 函数的顺序按函数名字典序正序排列getValue.Method(i).Call(args[i])fmt.Println("")}
}

相关文章:

【golang/go语言】Go语言之反射

本文参考了李文周的博客——Go语言基础之反射。 一、反射初识 1. 什么是反射 在计算机科学中&#xff0c;反射是指计算机程序在运行时&#xff08;run time&#xff09;可以访问、检测和修改它本身状态和行为的一种能力。用比喻来说&#xff0c;反射就是程序在运行的时候能够…...

Java+Swing+Mysql实现超市管理系统

一、系统介绍1.开发环境操作系统&#xff1a;Win10开发工具 &#xff1a;IDEA2018JDK版本&#xff1a;jdk1.8数据库&#xff1a;Mysql8.02.技术选型JavaSwingMysql3.功能模块4.系统功能1.系统登录登出管理员可以登录、退出系统2.商品信息管理管理员可以对商品信息进行查询、添加…...

华为OD机试题,用 Java 解【机器人走迷宫】问题

最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…...

软件测试基本概念

软件测试基本概念 1. 什么是软件测试 软件测试就是验证软件产品特性(功能, 界面, 兼容性, 性能…)是否符合用户的需求&#xff0c;同时软件测试不仅要测试系统是否做了其应该做的, 还需要测试系统是否未作其不应该做的。 2. 调试与测试 软件测试与调试的区别&#xff1a; …...

数学建模介绍

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a; &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对我最大的激励…...

【LVGL】学习笔记--(2)GUI Guider的使用

基于上一篇【LVGL】学习笔记--(1)Keil中嵌入式系统移植LVGL&#xff0c;已经成功地移植了LVGL到我们的嵌入式板子上&#xff0c;并配合磁控旋钮编码器&#xff08;或者诸如触摸屏、按键、键盘等其他输入设备均可&#xff09;&#xff0c;实现了简单界面的显示工作。这一章将学习…...

OpenCV-PyQT项目实战(6)项目案例02:滚动条应用

欢迎关注『OpenCV-PyQT项目实战 Youcans』系列&#xff0c;持续更新中 OpenCV-PyQT项目实战&#xff08;1&#xff09;安装与环境配置 OpenCV-PyQT项目实战&#xff08;2&#xff09;QtDesigner 和 PyUIC 快速入门 OpenCV-PyQT项目实战&#xff08;3&#xff09;信号与槽机制 …...

3 决策树及Python实现

1 主要思想 1.1 数据 1.2 训练和使用模型 训练&#xff1a;建立模型&#xff08;树&#xff09; 测试&#xff1a;使用模型&#xff08;树&#xff09; Weka演示ID3&#xff08;终端用户模式&#xff09; 双击weka.jar选择Explorer载入weather.arff选择trees–>ID3构建树…...

小程序和Vue+uniapp+unicloud培训课件

文章目录**一、什么是小程序****1.1** **小程序简介****1.2** **小程序的特点****1.3** **小程序的开发流程**个人小程序和企业小程序的区别1.4 小程序代码构成1.4.1 JSON 配置1.4.2 WXML 模板**数据绑定**逻辑语法条件逻辑列表渲染模板引用共同属性1.4.3 WXSS 样式1.4.4 JS 逻…...

C语言--指针进阶2

目录前言函数指针函数指针数组指向函数指针数组的指针回调函数前言 本篇文章我们将继续学习指针进阶的有关内容 函数指针 我们依然用类比的方法1来理解函数指针这一全新的概念&#xff0c;如图1 我们用一段代码来验证一下&#xff1a; int Add(int x, int y) {return xy;…...

【步进电机和 Arduino】

【步进电机和 Arduino】 前言1. 什么是步进电机及其工作原理?1.1 步进电机结构1.2 绕线方式1.3 通电方式2. 如何使用Arduino和A17步进驱动器控制NEMA4988步进电机2.1 A4988 和 Arduino 连接2.2 测量AB相2.3 A4988 限流3. 步进电机和 Arduino3.1 示例代码 13.2 示例代码 24. 使…...

【面试一:|和||、和区别】

相同点&#xff1a; ||和&&都是逻辑运算符&#xff0c;而|和&是位运算符。位运算符的优先级要比逻辑运算符的优先级高。 &和&&的区别 &和&&都可以用作逻辑与的运算符&#xff0c;表示逻辑与&#xff08;and&#xff09;&#xff0c;当运…...

【一天一门编程语言】使用汇编语言实现斐波那契数列

文章目录使用汇编语言实现斐波那契数列一、什么是斐波那契数列二、如何用汇编语言实现斐波那契数列一、汇编语言概念1.1 什么是汇编语言1.2 汇编语言的特点二、汇编语言指令2.1 简单指令2.2 复杂指令汇编语言程序结构代码实例指令集常用指令指令代码实例使用汇编语言实现斐波那…...

RabbitMQ实现死信队列

目录死信队列是什么怎样实现一个死信队列说明实现过程导入依赖添加配置编写mq配置类添加业务队列的消费者添加死信队列的消费者添加消息发送者添加消息测试类测试死信队列的应用场景总结死信队列是什么 “死信”是RabbitMQ中的一种消息机制&#xff0c;当你在消费消息时&#…...

【Linux】安装Tomcat教程

目录 1.上传安装包 2.解压安装包 3.启动Tomcat 4.查看启动日志 5.查看进程 6.开放端口 7.停止Tomcat 1.上传安装包 使用FinalShell自带的上传工具将Tomcat的二进制发布包上传到Linux(与前面上传JDK安装包步骤 一致)。 2.解压安装包 将上传上来的安装包解压到指定目录…...

学习笔记之Vuex(五)

Vuex&#xff08;五&#xff09;Vuex一、什么是Vuex二、Vuex工作原理三、搭建Vuex环境四、求和案例分析4.1 求和案例——vue实现4.2 求和案例——vuex实现&#xff08;五&#xff09;Vuex 一、什么是Vuex 1.概念 在Vue中实现集中式状态&#xff08;数据&#xff09;管理的一…...

SSM知识快速复习

SSM知识快速复习SpringIOCDIIOC容器在Spring中的实现常用注解Autowired注解的原理AOP相关术语作用动态代理实现原理事务Transactional事务属性&#xff1a;只读事务属性&#xff1a;超时事务属性&#xff1a;回滚策略事务属性&#xff1a;事务隔离级别事务属性&#xff1a;事务…...

【Linux】安装MySQL

目录 1.检测当前系统是否安装过MySQL相关数据库 2. 卸载现有的MySQL数据库 3.上传解压 4.顺序安装rpm包 5.启动MySQL 6.查看临时密码 7.登录MySQL 8.开放端口 1.检测当前系统是否安装过MySQL相关数据库 需要通过rpm相关指令&#xff0c;来查询当前系统中是否存在已安…...

【深度学习】手把手教你开发自己的深度学习模板

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言1数据相关1.1 数据初探1.2.数据处理1.3 数据变形2 定义网络&#xff0c;优化函数3. 训练前言 入坑2年后&#xff0c;重新梳理之前的知识&#xff0c;发现其实需…...

一个诡异的 Pulsar InterruptedException 异常

背景 今天收到业务团队反馈线上有个应用往 Pulsar 中发送消息失败了&#xff0c;经过日志查看得知是发送消息时候抛出了 java.lang.InterruptedException 异常。 和业务沟通后得知是在一个 gRPC 接口中触发的消息发送&#xff0c;大约持续了半个小时的异常后便恢复正常了&…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf

FTP 客服管理系统 实现kefu123登录&#xff0c;不允许匿名访问&#xff0c;kefu只能访问/data/kefu目录&#xff0c;不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

比较数据迁移后MySQL数据库和OceanBase数据仓库中的表

设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

Vue 模板语句的数据来源

&#x1f9e9; Vue 模板语句的数据来源&#xff1a;全方位解析 Vue 模板&#xff08;<template> 部分&#xff09;中的表达式、指令绑定&#xff08;如 v-bind, v-on&#xff09;和插值&#xff08;{{ }}&#xff09;都在一个特定的作用域内求值。这个作用域由当前 组件…...

C++实现分布式网络通信框架RPC(2)——rpc发布端

有了上篇文章的项目的基本知识的了解&#xff0c;现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...

消防一体化安全管控平台:构建消防“一张图”和APP统一管理

在城市的某个角落&#xff0c;一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延&#xff0c;滚滚浓烟弥漫开来&#xff0c;周围群众的生命财产安全受到严重威胁。就在这千钧一发之际&#xff0c;消防救援队伍迅速行动&#xff0c;而豪越科技消防一体化安全管控平台构建的消防“…...