Go语言反射(reflect)

反射是应用程序检查其所拥有的结构,尤其是类型的一种能。每种语言的反射模型都不同,并且有些语言根本不支持反射。Go语言实现了反射,反射机制就是在运行时动态调用对象的方法和属性,即可从运行时态的示例对象反求其编码阶段的定义,标准库中reflect包提供了相关的功能。在reflect包中,通过reflect.TypeOf(),reflect.ValueOf()分别从类型、值的角度来描述一个Go对象。

func TypeOf(i interface{}) Type
type Type interface func ValueOf(i interface{}) Value
type Value struct 

在Go语言的实现中,一个interface类型的变量存储了2个信息, 一个<值,类型>对,<value,type> :

(value, type)

value是实际变量值,type是实际变量的类型。两个简单的函数,reflect.TypeOf 和 reflect.ValueOf,返回被检查对象的类型和值。

例如,x 被定义为:var x float64 = 3.4,那么 reflect.TypeOf(x) 返回 float64,reflect.ValueOf(x) 返回 3.4。实际上,反射是通过检查一个接口的值,变量首先被转换成空接口。这从下面两个函数签名能够很明显的看出来:

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value

reflect.Type 和 reflect.Value 都有许多方法用于检查和操作它们。

Type主要有:
Kind() 将返回一个常量,表示具体类型的底层类型
Elem()方法返回指针、数组、切片、字典、通道的基类型,这个方法要慎用,如果用在其他类型上面会出现panic

Value主要有:
Type() 将返回具体类型所对应的 reflect.Type(静态类型)
Kind() 将返回一个常量,表示具体类型的底层类型

反射可以在运行时检查类型和变量,例如它的大小、方法和 动态 的调用这些方法。这对于没有源代码的包尤其有用。

由于反射是一个强大的工具,但反射对性能有一定的影响,除非有必要,否则应当避免使用或小心使用。下面代码针对int、数组以及结构体分别使用反射机制,其中的差异请看注释。

package mainimport ("fmt""reflect"
)type Student struct {name string
}func main() {var a int = 50v := reflect.ValueOf(a) // 返回Value类型对象,值为50t := reflect.TypeOf(a)  // 返回Type类型对象,值为intfmt.Println(v, t, v.Type(), t.Kind())var b [5]int = [5]int{5, 6, 7, 8}fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(),reflect.TypeOf(b).Elem()) // [5]int array intvar Pupil Studentp := reflect.ValueOf(Pupil) // 使用ValueOf()获取到结构体的Value对象fmt.Println(p.Type()) // 输出:Studentfmt.Println(p.Kind()) // 输出:struct}

在Go语言中,类型包括 static type和concrete type. 简单说 static type是你在编码是看见的类型(如int、string),concrete type是实际具体的类型,runtime系统看见的类型。

Type()返回的是静态类型,而kind()返回的是具体类型。上面代码中,在int,数组以及结构体三种类型情况中,可以看到kind(),type()返回值的差异。

通过反射可以修改原对象

d.CanAddr()方法:判断它是否可被取地址
d.CanSet()方法:判断它是否可被取地址并可被修改

通过一个settable的Value反射对象来访问、修改其对应的变量值:

package mainimport ("fmt""reflect"
)type Student struct {name stringAge  int
}func main() {var a int = 50v := reflect.ValueOf(a) // 返回Value类型对象,值为50t := reflect.TypeOf(a)  // 返回Type类型对象,值为intfmt.Println(v, t, v.Type(), t.Kind(), reflect.ValueOf(&a).Elem())seta := reflect.ValueOf(&a).Elem() // 这样才能让seta保存a的值fmt.Println(seta, seta.CanSet())seta.SetInt(1000)fmt.Println(seta)var b [5]int = [5]int{5, 6, 7, 8}fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(), reflect.TypeOf(b).Elem())var Pupil Student = Student{"joke", 18}p := reflect.ValueOf(Pupil) // 使用ValueOf()获取到结构体的Value对象fmt.Println(p.Type()) // 输出:Studentfmt.Println(p.Kind()) // 输出:structsetStudent := reflect.ValueOf(&Pupil).Elem()//setStudent.Field(0).SetString("Mike") // 未导出字段,不能修改,panic会发生setStudent.Field(1).SetInt(19)fmt.Println(setStudent)}

虽然反射可以越过Go语言的导出规则的限制读取结构体中未导出的成员,但不能修改这些未导出的成员。因为一个结构体中只有被导出的字段才是可修改的。

在结构体中有tag标签,通过反射可获取结构体成员变量的tag信息。

package mainimport ("fmt""reflect"
)type Student struct {name stringAge  int `json:"years"`
}func main() {var Pupil Student = Student{"joke", 18}setStudent := reflect.ValueOf(&Pupil).Elem()sSAge, _ := setStudent.Type().FieldByName("Age")fmt.Println(sSAge.Tag.Get("json")) // years
}
程序输出:
years

反射结构体

为了完整说明反射的情况,通过反射一个结构体类型,综合来说明。下面例子较为系统地利用一个结构体,来充分举例说明反射:

package mainimport ("fmt""reflect"
)// 结构体
type ss struct {intstringboolfloat64
}func (s ss) Method1(i int) string  { return "结构体方法1" }
func (s *ss) Method2(i int) string { return "结构体方法2" }var (structValue = ss{ // 结构体20, "结构体", false, 64.0, }
)// 复杂类型
var complexTypes = []interface{}{structValue, &structValue, // 结构体structValue.Method1, structValue.Method2, // 方法
}func main() {// 测试复杂类型for i := 0; i < len(complexTypes); i++ {PrintInfo(complexTypes[i])}
}func PrintInfo(i interface{}) {if i == nil {fmt.Println("--------------------")fmt.Printf("无效接口值:%v\n", i)fmt.Println("--------------------")return}v := reflect.ValueOf(i)PrintValue(v)
}func PrintValue(v reflect.Value) {fmt.Println("--------------------")// ----- 通用方法 -----fmt.Println("String             :", v.String())  // 反射值的字符串形式fmt.Println("Type               :", v.Type())    // 反射值的类型fmt.Println("Kind               :", v.Kind())    // 反射值的类别fmt.Println("CanAddr            :", v.CanAddr()) // 是否可以获取地址fmt.Println("CanSet             :", v.CanSet())  // 是否可以修改if v.CanAddr() {fmt.Println("Addr               :", v.Addr())       // 获取地址fmt.Println("UnsafeAddr         :", v.UnsafeAddr()) // 获取自由地址}// 获取方法数量fmt.Println("NumMethod          :", v.NumMethod())if v.NumMethod() > 0 {// 遍历方法i := 0for ; i < v.NumMethod()-1; i++ {fmt.Printf("    ┣ %v\n", v.Method(i).String())//            if i >= 4 { // 只列举 5 个//                fmt.Println("    ┗ ...")//                break//            }}fmt.Printf("    ┗ %v\n", v.Method(i).String())// 通过名称获取方法fmt.Println("MethodByName       :", v.MethodByName("String").String())}switch v.Kind() {// 结构体:case reflect.Struct:fmt.Println("=== 结构体 ===")// 获取字段个数fmt.Println("NumField           :", v.NumField())if v.NumField() > 0 {var i int// 遍历结构体字段for i = 0; i < v.NumField()-1; i++ {field := v.Field(i) // 获取结构体字段fmt.Printf("    ├ %-8v %v\n", field.Type(), field.String())}field := v.Field(i) // 获取结构体字段fmt.Printf("    └ %-8v %v\n", field.Type(), field.String())// 通过名称查找字段if v := v.FieldByName("ptr"); v.IsValid() {fmt.Println("FieldByName(ptr)   :", v.Type().Name())}// 通过函数查找字段v := v.FieldByNameFunc(func(s string) bool { return len(s) > 3 })if v.IsValid() {fmt.Println("FieldByNameFunc    :", v.Type().Name())}}}
}
程序输出:
String             : <main.ss Value>
Type               : main.ss
Kind               : struct
CanAddr            : false
CanSet             : false
NumMethod          : 1<func(int) string Value>
MethodByName       : <invalid Value>
=== 结构体 ===
NumField           : 4int      <int Value>string   结构体├ bool     <bool Value>float64  <float64 Value>
--------------------
String             : <*main.ss Value>
Type               : *main.ss
Kind               : ptr
CanAddr            : false
CanSet             : false
NumMethod          : 2<func(int) string Value><func(int) string Value>
MethodByName       : <invalid Value>
--------------------
String             : <func(int) string Value>
Type               : func(int) string
Kind               : func
CanAddr            : false
CanSet             : false
NumMethod          : 0
--------------------
String             : <func(int) string Value>
Type               : func(int) string
Kind               : func
CanAddr            : false
CanSet             : false
NumMethod          : 0

细心的读者可能发现了上面代码中的一个有趣的问题,那就是structValue, &structValue的反射结果是不一样的,指针对象在这里有两个方法,而值对象只有一个方法,这是因为Method2()方法是指针方法,在值对象中是不能被反射到的。

本文节选于Go合集《Go语言四十二章经》:GOLANG ROADMAP 一个专注Go语言学习、求职的社区。

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

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

相关文章

LeetCode_21_简单_合并两个有序链表

文章目录 1. 题目2. 思路及代码实现&#xff08;Python&#xff09;2.1 递归2.2 迭代 1. 题目 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a; l 1 [ 1 , 2 , 4 ] , l 2 [ 1 , 3 …

MongoDB聚合运算符:$cmp

文章目录 语法用法举例 $cmp聚合运算符返回连个值的比较结果。 语法 { $cmp: [ <expression1>, <expression2> ] }表达式可以是任何类型&#xff0c;使用标准的BSON比较顺序对不同类型的值进行比较。 用法 $cmp对两个值进行比较&#xff0c;返回&#xff1a; …

SQL注入漏洞解析-less-8(布尔盲注)

我们来看一下第八关 当我们进行尝试时&#xff0c;他只有You are in...........或者没有显示。 他只有对和错显示&#xff0c;那我们只能用对或者错误来猜他这个数据库 ?id1%27%20and%20ascii(substr(database(),1,1))>114-- ?id1%27%20and%20ascii(substr(database(),1,…

[WebUI Forge]ForgeUI的安装与使用 | 相比较于Auto1111 webui 6G显存速度提升60-75%

ForgeUI的github主页地址:https://github.com/lllyasviel/stable-diffusion-webui-forge Stable Diffusion WebUI Forge 是一个基于Stable Diffusion WebUI(基于Gradio)的平台,可简化开发、优化资源管理并加快推理速度。 “Forge”这个名字的灵感来自于“Minecraft Forge”…

Spring MVC HandlerMapping原理解析

在Spring MVC框架中&#xff0c;HandlerMapping是一个核心组件&#xff0c;负责将客户端的请求映射到相应的处理器&#xff08;Handler&#xff09;。理解HandlerMapping的原理对于掌握Spring MVC的请求处理机制至关重要。本文将对HandlerMapping的原理进行详细分析&#xff0c…

【MySQL】MySQL复合查询--多表查询自连接子查询 - 副本 (2)

文章目录 1.基本查询回顾2.多表查询3.自连接4.子查询 4.1单行子查询4.2多行子查询4.3多列子查询4.4在from子句中使用子查询4.5合并查询 4.5.1 union4.5.2 union all 1.基本查询回顾 表的内容如下&#xff1a; mysql> select * from emp; ----------------------------…

vue项目导出excel ,文件过大导致请求超时的处理方法

一、因为文件过大&#xff0c;请求时间较长&#xff0c;就会产生请求超时的情况&#xff0c;处理方式是可以分为三个接口&#xff0c;接口1用来获取id值&#xff0c;接口2利用id值发起请求&#xff0c;询问是否准备好下载&#xff0c;如果没准备好&#xff0c;则没隔一秒再次发…

便携式森林消防灭火泵:森林安全的守护者

在自然环境中&#xff0c;森林是地球生态系统的重要组成部分&#xff0c;它们为我们提供氧气、净化空气、防止土壤侵蚀等重要功能。然而&#xff0c;当森林发生火灾时&#xff0c;它们也会成为我们的噩梦。火势蔓延迅速&#xff0c;难以控制&#xff0c;对森林和生态环境造成严…

HTML5 CSS3 提高

一&#xff0c;HTML5的新特性 这些新特性都有兼容性问题&#xff0c;基本是IE9以上版本的浏览器才支持&#xff0c;如果不考虑兼容性问题&#xff0c;可以大量使用这些新特性。 1.1新增语义化标签 注意&#xff1a; 1这种语义化标签主要是针对搜索引擎的 2这些新标签在页面…

FL Studio 21 Mac汉化免费版 附安装教程

FL Studio 21 Mac是Mac系统中的一款水果音乐编辑软件&#xff0c;提供多种插件&#xff0c;包括采样器、合成器和效果器&#xff0c;可编辑不同风格的音乐作品&#xff0c;Pattern/Song双模式&#xff0c;可兼容第三方插件和音效包&#xff0c;为您的创意插上翅膀。FL Studio 2…

尚硅谷webpack5笔记base部分

base 基本使用 Webpack 是一个静态资源打包工具。 它会以一个或多个文件作为打包的入口,将我们整个项目所有文件编译组合成一个或多个文件输出出去。 输出的文件就是编译好的文件,就可以在浏览器段运行了。 我们将 Webpack 输出的文件叫做 bundle。 功能介绍 Webpack …

textbox跨线程写入

实现实例1 实现效果 跨线程实现 // 委托&#xff0c;用于定义在UI线程上执行的方法签名 //public delegate void SetTextCallback(string text);public void textBoxText(string text){// 检查调用线程是否是创建控件的线程 if (textBox1.InvokeRequired){// 如果不是&#…

Adobe illustrator CEP插件调试

1.创建插件CEP面板&#xff0c;可以参考&#xff1a;http://blog.nullice.com/%E6%8A%80%E6%9C%AF/CEP-%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B/%E6%8A%80%E6%9C%AF-CEP-%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8B-Adobe-CEP-%E6%89%A9%E5%B1%95%E5%BC%80%E5%8F%91%E6%95%99%E7%A8%8…

【数据结构】OJ面试题《设计循环队列》(题库+代码)

1.前言 本题需要结构体和数组的知识&#xff0c;记录每天的刷题&#xff0c;继续坚持&#xff01; 2.OJ题目训练 设计循环队列 设计你的循环队列实现。 循环队列是一种线性数据结构&#xff0c;其操作表现基于 FIFO&#xff08;先进先出&#xff09;原则并且队尾被连接在队…

Firefox Focus,一个 “专注“ 的浏览器

近期才开始使用 Firefox Focus&#xff0c;虽然使用频率其实并不高&#xff0c;基本上只有想到了才去用&#xff0c;但每次使用的体验都很不错。 Firefox Focus 这款浏览器大约在 2015 年首次发布&#xff0c;不同于一般版本的 Firefox&#xff0c;它主打“自动删除浏览记录”…

Window10安装ruby

最好的方法&#xff0c;使用rubyinstaller&#xff0c;即在Downloads。 这是官方推荐的安装方式 通常来说我们会下载64位的 下载完后执行下载的exe即可。在最后一步会提示让安装gem&#xff0c;选则安装即可。 然后就可以在控制台进行测试了。

黑马程序员——接口测试——day03——Postman断言、关联、参数化

目录&#xff1a; Potman断言 Postman断言简介Postman常用断言 断言响应状态码断言包含某字符串断言JSON数据Postman断言工作原理Postman关联 简介实现步骤核心代码创建环境案例1案例2Postman参数化 简介数据文件简介编写数据文件 CSV文件JSON文件导入数据文件到postman读取数…

Unity IK 反向动力学 学习笔记

目录 Unity IK 反向动力学 ik 示例代码&#xff1a; Unity IK 反向动力学 “IK是Inverse Kinematic的缩写&#xff0c;也就是反向动力学。是根据骨骼的终节点来推算其他父节点的位置的一种方法。比如通过手的位置推算手腕、胳膊肘的骨骼的位置。” 适用的场景&#xff1a;比…

亿道信息新品EM-T195轻薄型工业平板,隆重登场!

EM-T195是一款轻巧但坚固的平板电脑&#xff0c;仅 650克重、10.5mm毫米厚&#xff0c;即使没有额外的便携配件进行辅助&#xff0c;您也可以轻松将其长时间随身携带。耐用性外壳完全密封&#xff0c;防尘防潮&#xff1b;出色的坚固性和可靠性&#xff0c;使T195天生适合在苛刻…

强大的Docker入门知识

目录 一、Docker简介 1.1、Docker是 1.2、Docker通常会在以下情况下使用&#xff1a; 1.3、Docker和VMware区别 1.4、Docker 的优点 二、环境配置 2.1、代码操作 2.2、效果演示 2.3、配置镜像仓库 开始配置 三、基本命令 3.1、Docker基本命令 3.2、Docker镜像常用…