【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 接口中触发的消息发送,大约持续了半个小时的异常后便恢复正常了&…...
Java岗面试题--Java并发(volatile 专题)
目录1. 面试题一:谈谈 volatile 的使用及其原理补充:内存屏障volatile 的原理2. 面试题二:volatile 为什么不能保证原子性3. 面试题三:volatile 的内存语义4. 面试题四:volatile 的实现机制5. 面试题五:vol…...
Java---打家劫舍ⅠⅡ
目录 打家劫舍Ⅰ 题目分析 代码一 代码二 打家劫舍Ⅱ 打家劫舍Ⅰ 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被…...
MySQL Lesson4
1:关于查询结果集的去重(distinct) select distinct job from emp; **distinct只能出现在所有字段的最前面。所表示的含有是所有的结果联合起来去重。 select distinct deptno,job from emp order by deptno; select count(distinct job)from…...
浅谈权限获取方法之文件上传
概述 文件上传漏洞是发生在有上传功能的应用中,如果应用程序对用户的上传文件没有控制或者存在缺陷,攻击者可以利用应用上传功能存在的缺陷,上传木马、病毒等有危害的文件到服务器上面,控制服务器。 漏洞成因及危害 文件上传漏…...
资产设备防拆标签安全防护和资产定位解决方案
随着社会经济的发展和高新技术的日新月异,对各方面的安全要求也在不断地提高,以物联网安防、入侵报警和出入口控制、应急系统等为主的安全防范系统日益成为各类文物场所智能化弱电工程不可或缺的组成部分,是重点资产管理场所内加强管理和安全…...
企业电子招标采购源码之电子招标投标全流程!
随着各级政府部门的大力推进,以及国内互联网的建设,电子招投标已经逐渐成为国内主流的招标投标方式,但是依然有很多人对电子招投标的流程不够了解,在具体操作上存在困难。虽然各个交易平台的招标投标在线操作会略有不同࿰…...
【考研408】计算机网络笔记
文章目录计算机网络体系结构计算机网络概述计算机网络的组成计算机网络的功能计算机网络的分类计算机网络的性能指标课后习题计算机网络体系结构与参考模型计算机网络协议、接口、服务的概念ISO/OSI参考模型和TCP/IP模型课后习题物理层通信基础基本概念奈奎斯特定理与香农定理编…...
[C++]继承
🥁作者: 华丞臧 📕专栏:【C】 各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞收藏关注)。如果有错误的地方,欢迎在评论区指出。 推荐一款刷题网站 👉LeetCode 文章目录一、继承…...
优化知识管理方法丨整理零碎信息,提高数据价值
信息流时代,知识成集合倍数增长,看似我们学习了很多知识,但知识零碎无系统,知识之间缺乏联系,没有深度,所以虽然你很努力,但你发现自己的能力增长特别缓慢,你需要整理知识将零散的知…...
Windows操作系统的体系结构、运行环境和运行状态
我是荔园微风,作为一名在IT界整整25年的老兵,今天我们来重新审视一下Windows这个我们熟悉的不能再熟悉的系统。说Windows操作系统的运行环境和运行状态,首先要介绍一下Windows操作系统的体系结构,然后再要说到最重要的两个概念:核…...
wordpress 自动上传插件/郑州搜狗关键词优化顾问
我有一个PHP REST API,可以托管Amazon S3中的所有图像.我正在寻找一个插件或技巧,使用GET参数来调整图像大小.例如:http://my-bucket.s3.amazon.com/image.jpg?width300&height300我找到了this plugin,但我的团队成员说它是基于ASP.NET的,不适合我的PHP API项目…...
html5做网站心得体会/搜索关键词排行榜
点击上方“机械设计一点通”关注我们,每天学习一个机械设计相关知识点CAD输入命令时提示未知命令“xxx”,常规处理方式CAD 有时输入命令,会提示未知命令“XXX”。按 F1 查看帮助。很多人提议重装软件,甚至重装系统;经过…...
快站微信网站制作/免费b站推广软件
最近准备玩一下Oracle Golden Gate,需要搭个新环境,安装过程记录一下。操作系统版本:Oracle Linux Server release 5.7Oracle版本:Oracle Database 11g Release 2 (11.2.0.1.0) for Linux x86一、安装前的环境配置1. 检查官方文档…...
西宁做腋臭哪里北大DE网站/网络推广的主要内容
图像自动配准 图像配准(Image Registration):将不同时间、不同传感器(成像设备)或不同条件下(天候、照度、摄像位置和角度等)获取的两幅或多副图像进行匹配、叠加的过程 自动配准流 操作&…...
利用分类信息网站做推广/网络营销服务公司
命令行操作 启动数据库服务 net start mysql关闭数据库服务 net stop mysql登录数据库 mysql -hlocalhost -uroot -p退出数据库 mysql>exit创建数据库 mysql>create database 库名;删除数据库 mysql>drop database 库名;查看数据库列表 mysql>show databas…...
做网站用中文路径/网站服务器速度对seo有什么影响
昨天开发的时候一直纠结一个问题,给a标签注册事件,却一直没有注册上! $(#myTab li a).click(function() {localStorage.setItem(tabId, $(this).attr("href"));});完全是一个普通的事件注册代码。今天排查的结论是,$(#m…...