【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. 什么是反射 在计算机科学中,反射是指计算机程序在运行时(run time)可以访问、检测和修改它本身状态和行为的一种能力。用比喻来说,反射就是程序在运行的时候能够…...
Java+Swing+Mysql实现超市管理系统
一、系统介绍1.开发环境操作系统:Win10开发工具 :IDEA2018JDK版本:jdk1.8数据库:Mysql8.02.技术选型JavaSwingMysql3.功能模块4.系统功能1.系统登录登出管理员可以登录、退出系统2.商品信息管理管理员可以对商品信息进行查询、添加…...
华为OD机试题,用 Java 解【机器人走迷宫】问题
最近更新的博客 华为OD机试题,用 Java 解【停车场车辆统计】问题华为OD机试题,用 Java 解【字符串变换最小字符串】问题华为OD机试题,用 Java 解【计算最大乘积】问题华为OD机试题,用 Java 解【DNA 序列】问题华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】使…...
软件测试基本概念
软件测试基本概念 1. 什么是软件测试 软件测试就是验证软件产品特性(功能, 界面, 兼容性, 性能…)是否符合用户的需求,同时软件测试不仅要测试系统是否做了其应该做的, 还需要测试系统是否未作其不应该做的。 2. 调试与测试 软件测试与调试的区别: …...
数学建模介绍
🚀write in front🚀 📜所属专栏: 🛰️博客主页:睿睿的博客主页 🛰️代码仓库:🎉VS2022_C语言仓库 🎡您的点赞、关注、收藏、评论,是对我最大的激励…...
【LVGL】学习笔记--(2)GUI Guider的使用
基于上一篇【LVGL】学习笔记--(1)Keil中嵌入式系统移植LVGL,已经成功地移植了LVGL到我们的嵌入式板子上,并配合磁控旋钮编码器(或者诸如触摸屏、按键、键盘等其他输入设备均可),实现了简单界面的显示工作。这一章将学习…...
OpenCV-PyQT项目实战(6)项目案例02:滚动条应用
欢迎关注『OpenCV-PyQT项目实战 Youcans』系列,持续更新中 OpenCV-PyQT项目实战(1)安装与环境配置 OpenCV-PyQT项目实战(2)QtDesigner 和 PyUIC 快速入门 OpenCV-PyQT项目实战(3)信号与槽机制 …...
3 决策树及Python实现
1 主要思想 1.1 数据 1.2 训练和使用模型 训练:建立模型(树) 测试:使用模型(树) Weka演示ID3(终端用户模式) 双击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来理解函数指针这一全新的概念,如图1 我们用一段代码来验证一下: 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. 使…...
【面试一:|和||、和区别】
相同点: ||和&&都是逻辑运算符,而|和&是位运算符。位运算符的优先级要比逻辑运算符的优先级高。 &和&&的区别 &和&&都可以用作逻辑与的运算符,表示逻辑与(and),当运…...
【一天一门编程语言】使用汇编语言实现斐波那契数列
文章目录使用汇编语言实现斐波那契数列一、什么是斐波那契数列二、如何用汇编语言实现斐波那契数列一、汇编语言概念1.1 什么是汇编语言1.2 汇编语言的特点二、汇编语言指令2.1 简单指令2.2 复杂指令汇编语言程序结构代码实例指令集常用指令指令代码实例使用汇编语言实现斐波那…...
RabbitMQ实现死信队列
目录死信队列是什么怎样实现一个死信队列说明实现过程导入依赖添加配置编写mq配置类添加业务队列的消费者添加死信队列的消费者添加消息发送者添加消息测试类测试死信队列的应用场景总结死信队列是什么 “死信”是RabbitMQ中的一种消息机制,当你在消费消息时&#…...
【Linux】安装Tomcat教程
目录 1.上传安装包 2.解压安装包 3.启动Tomcat 4.查看启动日志 5.查看进程 6.开放端口 7.停止Tomcat 1.上传安装包 使用FinalShell自带的上传工具将Tomcat的二进制发布包上传到Linux(与前面上传JDK安装包步骤 一致)。 2.解压安装包 将上传上来的安装包解压到指定目录…...
学习笔记之Vuex(五)
Vuex(五)Vuex一、什么是Vuex二、Vuex工作原理三、搭建Vuex环境四、求和案例分析4.1 求和案例——vue实现4.2 求和案例——vuex实现(五)Vuex 一、什么是Vuex 1.概念 在Vue中实现集中式状态(数据)管理的一…...
SSM知识快速复习
SSM知识快速复习SpringIOCDIIOC容器在Spring中的实现常用注解Autowired注解的原理AOP相关术语作用动态代理实现原理事务Transactional事务属性:只读事务属性:超时事务属性:回滚策略事务属性:事务隔离级别事务属性:事务…...
【Linux】安装MySQL
目录 1.检测当前系统是否安装过MySQL相关数据库 2. 卸载现有的MySQL数据库 3.上传解压 4.顺序安装rpm包 5.启动MySQL 6.查看临时密码 7.登录MySQL 8.开放端口 1.检测当前系统是否安装过MySQL相关数据库 需要通过rpm相关指令,来查询当前系统中是否存在已安…...
【深度学习】手把手教你开发自己的深度学习模板
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言1数据相关1.1 数据初探1.2.数据处理1.3 数据变形2 定义网络,优化函数3. 训练前言 入坑2年后,重新梳理之前的知识,发现其实需…...
一个诡异的 Pulsar InterruptedException 异常
背景 今天收到业务团队反馈线上有个应用往 Pulsar 中发送消息失败了,经过日志查看得知是发送消息时候抛出了 java.lang.InterruptedException 异常。 和业务沟通后得知是在一个 gRPC 接口中触发的消息发送,大约持续了半个小时的异常后便恢复正常了&…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...
2.3 物理层设备
在这个视频中,我们要学习工作在物理层的两种网络设备,分别是中继器和集线器。首先来看中继器。在计算机网络中两个节点之间,需要通过物理传输媒体或者说物理传输介质进行连接。像同轴电缆、双绞线就是典型的传输介质,假设A节点要给…...
Linux-进程间的通信
1、IPC: Inter Process Communication(进程间通信): 由于每个进程在操作系统中有独立的地址空间,它们不能像线程那样直接访问彼此的内存,所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...
