Go 语言中的反射

今天主要来聊聊 Go 语言中反射,希望对你有新的认知

虽然很多人使用 Go 语言有一定时间了,甚至有的使用了 1 年 2 年,然后对于 Go 语言中的反射还是模棱两可,使用起来的时候,心里也不是非常有底气

更有甚者,几乎不使用反射,当然,也不是什么错,在工作中能用最简单最高效,又可扩展,性能还好的方式来进行处理自然是最 nice ,没有必要去生搬硬套一些高级用法,毕竟工作不是我们的试炼场,可以自己下来多多实验,本次就来好好看看如何去玩反射

文章分别从如下五个方面来聊

  • 反射是什么
  • 反射的规则
  • 使用案例并灵活运用
  • 反射原理
  • 总结

简单来看反射是什么

简单来看,反射就是在程序运行时期对程序本身进行访问和修改的能力,例如在程序运行时,可以修改程序的字段名称,字段值,还可以给程序提供接口访问的信息等等

这是 Go 语言中提供的一种机制,我们可以在 Go 语言公共库中可以看到很多关于 reflect 的使用位置

例如常用的 fmt 包,常用的 json 序列化和反序列化,自然前面我们说到的 gorm 库自然也是使用了反射的

可是我们一般为什么要使用反射呢?

根据反射的能力,自然是因为我们提供的接口并不知道传入的数据类型会是什么样的, 只有当程序运行的时候才知道具体的数据类型

但是我们编码的时候又期望去校验程序运行时传入的类型会是什么样的(例如 json 的序列化)并对其这种具体的数据进行操作,这个时候,咱们就需要用到反射的能力了

所以对于使用到反射的地方,你都能看到 interface{} 是不是就不奇怪了呢?

正是因为不确定传入的数据类型会是什么样的,所以才设计成 interface{} ,那么如果对于 interface 有还不清楚他的特点和使用方式的,可以查看历史文章:

  • 关于 interface{} 会有啥注意事项?上
  • 关于 interface{} 会有啥注意事项?下

先关注反射的规则

首先关注反射的三个重要的定律,知道规则之后,我们按照规则玩就不会有什么问题,只有当我们不清楚规则,总是触发条款的时候,才会出现奇奇怪怪的问题

  1. 反射是可以将 接口类型的变量 转换成 反射类型的对象
  1. 反射可以将 反射类型的对象 转换成 接口类型的变量
  1. 我们在运行时要去修改的 反射类型的对象 ,那么要求这个对象对应的值是要可写的

对于上述 3 个规则也是比较好理解,还记的之前我们说过的 unsafe 包里面的指针吗?

都是将我们常用的数据类型,转换成包(例如 unsafe包,或者 reflect 包)里面的指定数据类型,然后再按照包里面的规则进行修改数据

相当于,换个马甲,就可以进行不同的操作了

关注使用案例并灵活运用

一般咱们先会基本的应用,再去研究他的原理,研究他为什么可以这样用,慢慢的才能理解的更加深刻

对于定律一,将 接口类型的变量 转换成 反射类型的对象

实际上此处说的 接口类型的变量 我们可以传入任意数据类型的变量,例如 int, float, string ,map, slice, struct 等等

反射类型的对象 这里就可以理解成 reflect 反射包中的 reflect.Type reflect.Value 对象,可以通过 reflect 包中提供的 TypeOfValueOf 函数得到

其中 reflect.Type 实际上是一个 interface ,他里面包含了各种接口需要进行实现,它里面提供了关于类型相关的信息

其中如下图可以查看到 reflect.Type 的所有方法,其中

  • 绿色的 是所有数据类型都是可以调用的
  • 红色的是 函数类型数据可以调用的
  • 黑色的是 Map,数组 Array,通道 Chan,指针 Ptr 或者 切片Slice 可以调用的
  • 蓝色的是结构体调用的
  • 黄色的是通道 channel 类型调用的

reflect.Value 实际上是一个 struct,根据这个 struct 还关联了一组方法,这里面存放了数据类型和具体的数据,通过查看其数据结构就可以看出

type Value struct {typ *rtypeptr unsafe.Pointerflag
}

看到此处的 unsafe.Pointer 是不是很熟悉,底层自然就可以将 unsafe.Pointer 转换成 uintptr,然后再修改其数据后,再转换回来,对于 Go 指针不太熟悉的可以查看这篇文章:

  • GO 中的指针?

写一个简单的 demo 就可以简单的获取到变量的数据类型和值

func main() {var demoStr string = "now reflect"fmt.Println("type:", reflect.TypeOf(demoStr))fmt.Println("value:", reflect.ValueOf(demoStr))
}

对于定律二,将 反射类型的对象 转换成 接口类型的变量

我们可以通过将 reflect.Value 类型转换成我们具体的数据类型,因为 reflect.Value 中有对应的 typ *rtype 以及 ptr unsafe.Pointer

例如我们可以 通过 reflect.Value 对象的 interface() 方法来处理

func main() {var demoStr string = "now reflect"fmt.Println("type:", reflect.TypeOf(demoStr))fmt.Println("value:", reflect.ValueOf(demoStr))var res stringres = reflect.ValueOf(demoStr).Interface().(string)fmt.Println("res == ",res)
}

对于定律三,修改反射类型的对象

首先我们看上书的 demo 代码,传入 TypeOfValueOf 的变量实际上也是一个拷贝,那么如果期望在反射类型的对象中修改其值,那么就需要拿到具体变量的地址然后再进行修改,前提是这个变量是可写的

举个例子你就能明白

func main() {var demoStr string = "now reflect"v := reflect.ValueOf(demoStr)fmt.Println("is canset ", v.CanSet())//v.SetString("hello world")   // 会panic}

可以先调用 reflect.Value 对象的 CanSet 查看是否可写,如果是可写的,我们再写,如果不可写就不要写了,否则会 panic

那么传入变量的地址就可以修改了??

传入地址的思路没有毛病,但是我们去设置值的方式有问题,因此也会出现上述的 panic 情况

此处仔细看能够明白,反射的对象 v 自然是不可修改的,我们应该找到 reflect.Value 里面具体具体的数据指针,那么才是可以修改的,可以使用 reflect.Value Elem 方法

稍微复杂一点的

看上了上述案例可能会觉得那么简单的案例,一演示就 ok,但是工作中一用就崩溃,那自然还是没有融会贯通,说明还没有消化好,再来一个工作中的例子

  • 一个结构体里面有 map,map 中的 key 是 string,value 是 []string
  • 需求是访问 结构体中 hobby 字段对应的 map key 为 sport 的切片的第1 个元素,并将其修改为 hellolworld

type RDemo struct {Name  stringAge   intMoney float32Hobby map[string][]string
}func main() {tmp := &RDemo{Name:  "xiaomiong",Age:   18,Money: 25.6,Hobby: map[string][]string{"sport": {"basketball", "football"},"food":  {"beef"},},}v := reflect.ValueOf(tmp).Elem()  // 拿到结构体对象h := v.FieldByName("Hobby")    // 拿到 Hobby 对象h1 := h.MapKeys()[0]    // 拿到 Hobby 的第 0 个keyfmt.Println("key1 name == ",h1.Interface().(string))sli := h.MapIndex(h1)    // 拿到 Hobby 的第 0 个key对应的对象str := sli.Index(1)      // 拿到切片的第 1 个对象fmt.Println(str.CanSet())str.SetString("helloworld")fmt.Println("tmp == ",tmp)
}

可以看到上述案例运行之后有时可以运行成功,有时会出现 panic 的情况,相信细心的 xdm 就可以看出来,是因为 map 中的 key 是 无序的导致的,此处也提醒一波,使用 map 的时候要注意这一点

看上述代码,是不是就能够明白咱们使用反射去找到对应的数据类型,然后按照数据类型进行处理数据的过程了呢

有需要的话,可以慢慢的去熟练反射包中涉及的函数,重点是要了解其三个规则,对象转换方式,访问方式,以及数据修改方式

反射原理

那么通过上述案例,可以知道关于反射中数据类型和数据指针对应的值是相当重要的,不同的数据类型能够用哪些函数这个需要注意,否则用错直接就会 panic

TypeOf

来看 TypeOf 的接口中涉及的数据结构

在 reflect 包中 rtype 是非常重要的,Go 中所有的类型都会包含这个结构,所以咱们反射可以应用起来,结构如下

// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {size       uintptrptrdata    uintptrhash       uint32 tflag      tflagalign      uint8fieldAlign uint8kind       uint8equal     func(unsafe.Pointer, unsafe.Pointer) boolgcdata    *byte str       nameOffptrToThis typeOff
}

其中可以看到此处的 rtype 的结构保持和 runtime/type.go 一致 ,都是关于数据类型的表示,以及对应的指针,关于这一块的说明和演示可以查看文末的 interface{} 处的内容

ValueOf

ValueOf 的源码中,我们可以看到,重要的是 emptyInterface 结构

// emptyInterface is the header for an interface{} value.
type emptyInterface struct {typ  *rtypeword unsafe.Pointer
}

emptyInterface 结构中有 rtype 类型的指针, word 自然是对应的数据的地址了

reflect.Value 对象中的方法也是非常的多,用起来和上述说到的 reflect.Type 接口中的功能类似

关于源码中涉及到的方法,就不再过多的赘述了,更多的还是需要自己多多实践才能体会的更好

殊不知,此处的 reflect.Value 也是可以转换成 reflect.Type ,可以查看源码中 reflect\value.gofunc (v Value) Type() Type {

其中 reflect.Valuereflect.Type ,和任意数据类型 可以相互这样来转换

如下图:

总结

至此,关于反射就聊到这里,一些关于源码的细节并没有详细说,更多的站在一个使用者的角度去看反射需要注意的点

关于反射,大多的人是建议少用,因为是会影响到性能,不过如果不太关注这一点,那么用起来还是非常方便的

高级功能自然也是双刃剑,你用不好就会 panic,如果你期望去使用他,那么就去更多的深入了解和一步一步的吃透他吧

大道至简,反射三定律,活学活用

感谢阅读,欢迎交流,点个赞,关注一波 再走吧

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

好了,本次就到这里

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是阿兵云原生,欢迎点赞关注收藏,下次见~

文中提到的技术点,感兴趣的可以查看这些文章:

  • Go 语言中 panic 和 recover 搭配使用
  • 关于 interface{} 会有啥注意事项?上
  • 关于 interface{} 会有啥注意事项?下
  • GO 中的指针?
  • 你真的知道 GO 中 nil 代表什么吗?

可以进入地址进行体验和学习:https://xxetb.xet.tech/s/3lucCI

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度学习_3_张量运算

代码: import torchimport osimport pandas as pd import numpy as npx torch.tensor([[1, 2, 3], [4, 5, 6]]) print(x) sumA x.sum(dim 0) print(sumA) print(torch.sum(x, dim 0, keepdim True)) print(x/torch.sum(x, dim 0, keepdim True) )

如何在会计面试中展现自己的优势?

在会计面试中展现自己的优势是非常重要的,因为这将决定你是否能够脱颖而出并获得这个职位。下面是一些可以帮助你展示自己优势的方法: 1. 准备充分:在面试前,确保你对公司的背景和业务有所了解。研究公司的财务报告和新闻&#xf…

【LeetCode】2.两数相加

目录 1 题目2 答案2.1 我写的(不对)2.2 更正 3 问题 1 题目 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返…

[LitCTF 2023]导弹迷踪

这道题相较于其他的分数类型的js题有一点不一样,他不是像常规的有用bp多次抓包修改最后得分来获取flag的。 本题将flag藏到了他的前端文件中本身没有任何难度,只是为了记录一种新的做法 按照我们平常做js的思路就是先随便玩一下然后bp抓包看得分或者抓包…

各大自动化测试框架对比

自动化测试是把以人为驱动的测试行为转化为机器执行的一种过程,主要是编写代码、脚本,让软件自动运行,发现缺陷,代替部分的手工测试;自动化测试可分为自动化性能测试、自动化功能测试,自动化功能测试包括了…

C#,工业化软件与院校软件的对比及编程语言的选择建议

飞机发动之之一,涡轮喷气航空发动机(JET ENGINE) 火箭发动机之一,俄罗斯RD-180煤油和液氧发动机(ROCKET ENGINE) 1 飞机发动机与火箭发动机的简明对比 2 工业软件与院校软件的简单对比 除了以上类似的对比…

地下城堡3魂之诗食谱,地下城堡3菜谱37种

地下城堡3魂之诗食谱大全,让你解锁制作各种美食的方法!不同的食材搭配不同的配方制作,食物效果和失效也迥异。但有时候我们可能会不知道如何制作这些食物,下面为您介绍地下城堡3菜谱37种。 关注【娱乐天梯】,获取内部福…

【Redis】Hash 哈希内部编码方式

Hash 哈希内部编码方式 哈希的内部编码有两种: ziplist(压缩列表):当哈希类型元素个数⼩于hash-max-ziplist-entries配置(默认512个)、同时所有值都⼩于hash-max-ziplist-value配置(默认64字节…

HDMI 基于 4 层 PCB 的布线指南

HDMI 基于 4 层 PCB 的布线指南 简介 HDMI 规范文件里面规定其差分线阻抗要求控制在 100Ω 15%,其中 Rev.1.3a 里面规定相对放宽了一些,容忍阻抗失控在 100Ω 25%范围内,不要超过 250ps。 通常,在 PCB 设计时,注意控…

初学vue,想自己找个中长期小型项目练练手,应该做什么?

前言 可以试着做一两个完整的后台管理项目后再去做其他的,下面推荐一些github上的vue后台管理的项目,可以自己选择性的练一下手 Vue2 1、iview-admin Star: 16.4k 基于 iview组件库开发的一款后台管理系统框架,提供了一系列的强大组件和基…

10、SpringBoot_测试用例

四、测试用例 1.准备工作 添加依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency><dependency><groupId>com…

RabbitMQ详细使用

工作队列 注意事项&#xff1a;一个消息只能被处理一次&#xff0c;不可以处理多次 轮询分发信息 消息应答 消费者在接收到消息并且处理该消息之后&#xff0c;告诉rabbitmq它已经处理了&#xff0c;rabbitmq可以把该消息删除了。倘若mq没有收到应答&#xff0c;mq会将消息转…

uniapp上echarts地图钻取

1: 预期效果 通过切换地图 , 实现地图的钻取效果 2: 实现原理以及核心方法/参数 一开始是想利用更换地图数据的形式进行地图钻取 , 这就意味着我们需要准备全国30多个省份的地图数据 , 由于一开始考虑需要适配小程序端 , 如此多的地图文件增加了程序的体积 , 如果使用接口调…

ubuntu22.04 电脑声音和腾讯会议等声音很小怎么修复

电脑的声音很小&#xff0c;特别是开微信会议的时候几乎听不到声音&#xff0c;而声音设置都已经设置为最大 这时候就是用命令 alsamixer腾讯会议的声音 调整这两个声音的大小即可 H键弹出帮助菜单 F6按键选择声卡 左右按键选择轨道 M键静音所选轨道 上下按键调整音量…

【数据结构】二叉树--链式结构的实现 (遍历)

目录 一 二叉树的遍历 1 构建一个二叉树 2 前序遍历 3 中序遍历 4 后续遍历 5 层序 6 二叉树销毁 二 应用(递归思想) 1 二叉树节点个数 2 叶子节点个数 3 第K层的节点个数 4 二叉树查找值为x的节点 5 判断是否是二叉树 一 二叉树的遍历 学习二叉树结构&#xff0…

HQChart支持的画图工具列表

HQChart支持的画图工具列表 线段类线段射线标价线垂直线箭头趋势线水平线水平线段平行射线平行线平行通道价格通道线M头W底头肩型波浪尺AB波浪尺箱型线涂鸦线段 图形类圆弧线矩形平行四边形三角形圆对称角度 文字类文本箭头NoteAnchoredTextPriceLabelPriceNote 其他江恩角度线…

Linux:redis集群(3.*版本 和 5.*版本)搭建方法

介绍 至少6个实例才能组成集群。3主3从会自动分配 Redis集群原理 Redis集群架构 Redis Cluster采用虚拟槽分区&#xff0c;将所有的数据根据算法映射到0~16383整数槽内 Redis Cluster是一个无中心的结构 每个节点都保存数据和整个集群的状态 集群角色 Master&#xff1a;Master…

YOLOv7改进策略:RIFormerBlock助力检测|CVPR2023 RIFormer:无需TokenMixer也能达成SOTA性能的极简ViT架构

💡💡💡本文属于原创独家改进: 稀疏重参数RIFormerBlock模型引入YOLOv7进行创新性 RIFormerBlock | 亲测在多个数据集实现涨点; 收录: YOLOv7高阶自研专栏介绍: http://t.csdnimg.cn/tYI0c ✨✨✨前沿最新计算机顶会复现 🚀🚀🚀YOLOv7自研创新结合,轻松…

Arduino安装esp32-cam以及CameraWebServer实例的实现

目录 1. 硬件2. 软件2.1 arduino下载安装2.2 arduino配置esp32-cam开发板文件2.3 下载相应版本的esp32 3. ESP32-CAM实例&#xff1a;CameraWebServer3.1 选择开发板3.2 选择示例代码进行验证3.3 烧录进行实测 1. 硬件 没买下载主板的可使用usb转串口模块进行烧录&#xff0c;接…

小程序:下拉刷新+上拉加载+自定义导航栏

下拉刷新 &#xff1a; <scroll-view scroll-y"true" 允许纵向滚动 refresher-enabled"true" 开启自定义下拉刷新 默认为false :refresher-triggered&quo…