李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
GO
正文
26.Golang之反射介绍
Leefs
2022-07-22 AM
691℃
0条
[TOC] ### 前言 反射(reflection)是在 Java 出现后迅速流行起来的一种概念,通过反射可以获取丰富的类型信息,并可以利用这些类型信息做非常灵活的工作。 ![26.Golang之反射介绍01.png](https://lilinchao.com/usr/uploads/2022/07/1990127710.png) ### 一、概述 **反射:**是指**在程序运行期对程序本身进行访问和修改的能力**。**程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分**。在运行程序时,程序无法获取自身的信息。 支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。 Go程序在运行期使用reflect包访问程序的反射信息。 #### 使用反射注意事项 + 与反射相关的代码,经常是难以阅读的。 + Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。 + 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。 ### 二、reflect包 在Go语言的反射机制中,任何接口值都是由`pair`对`一个具体类型(type)`和`具体类型的值(data)`两部分组成的(*该内容在之前章节《Golang静态类型与动态类型》中介绍过*)。 在Go语言中反射的相关功能由内置的reflect包提供,`reflect` 实现了运行时的反射能力,能够让程序操作不同类型的对象。 **反射包中有两对非常重要的函数和类型,两个函数分别是:** - `reflect.TypeOf`: 能获取类型信息; - `reflect.ValueOf`:能获取数据的运行时表示。 #### 2.1 TypeOf 在Go语言中,使用`reflect.TypeOf()`函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。 ```go package main import ( "fmt" "reflect" ) func reflectType(x interface{}) { v := reflect.TypeOf(x) fmt.Printf("type:%v\n", v) } func main() { var a float32 = 3.14 reflectType(a) // type:float32 var b int64 = 100 reflectType(b) // type:int64 } ``` ##### **反射的类型(Type)与种类(Kind)** 在使用反射时,需要首先理解类型(Type)和种类(Kind)的区别。 编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。 例如需要统一判断类型中的指针时,使用种类(Kind)信息就较为方便。 **(1)反射种类(Kind)的定义** Go语言程序中的类型(Type)指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。 例如使用 `type A struct{}` 定义结构体时,A 就是 `struct{}` 的类型。 种类(Kind)指的是对象归属的品种,在 reflect 包中有如下定义: ```go 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 // 底层指针 ) ``` Map、Slice、Chan 属于引用类型,使用起来类似于指针,但是在种类常量定义中仍然属于独立的种类,不属于 `Ptr`。`type A struct{}` 定义的结构体属于 `Struct` 种类,`*A` 属于 `Ptr`。 **(2)从类型对象中获取类型名称和种类** + Go语言中的**类型名称**对应的反射获取方法是 `reflect.Type` 中的 Name() 方法,返回表示类型名称的字符串; + 类型归属的种类(Kind)使用的是 `reflect.Type` 中的 Kind() 方法,返回 `reflect.Kind` 类型的常量。 **示例** ```go package main import ( "fmt" "reflect" ) type myInt int64 func reflectType(x interface{}) { t := reflect.TypeOf(x) fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind()) } func main() { var a *float32 // 指针 var b myInt // 自定义类型 var c rune // 类型别名 reflectType(a) // type: kind:ptr reflectType(b) // type:myInt kind:int64 reflectType(c) // type:int32 kind:int32 type person struct { name string age int } type book struct{ title string } var d = person{ name: "Leefs", age: 20, } var e = book{title: "《GO语言圣经》"} reflectType(d) // type:person kind:struct reflectType(e) // type:book kind:struct } ``` #### 2.2 ValueOf `reflect.ValueOf()`返回的是`reflect.Value`类型,其中包含了原始值的值信息。 `reflect.Value`与原始值之间可以互相转换。 **`reflect.Value`类型提供的获取原始值的方法如下:** | 方法 | 说明 | | :----------------------: | :----------------------------------------------------------: | | Interface() interface {} | 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型 | | Int() int64 | 将值以 int 类型返回,所有有符号整型均可以此方式返回 | | Uint() uint64 | 将值以 uint 类型返回,所有无符号整型均可以此方式返回 | | Float() float64 | 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回 | | Bool() bool | 将值以 bool 类型返回 | | Bytes() []bytes | 将值以字节数组 []bytes 类型返回 | | String() string | 将值以字符串类型返回 | ##### 通过反射获取值 ```go import ( "fmt" "reflect" ) func reflectValue(x interface{}) { v := reflect.ValueOf(x) k := v.Kind() switch k { case reflect.Int64: // v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换 fmt.Printf("type is int64, value is %d\n", int64(v.Int())) case reflect.Float32: // v.Float()从反射中获取浮点型的原始值,然后通过float32()强制类型转换 fmt.Printf("type is float32, value is %f\n", float32(v.Float())) case reflect.Float64: // v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换 fmt.Printf("type is float64, value is %f\n", float64(v.Float())) } } func main() { var a float32 = 3.14 var b int64 = 100 reflectValue(a) // type is float32, value is 3.140000 reflectValue(b) // type is int64, value is 100 // 将int类型的原始值转换为reflect.Value类型 c := reflect.ValueOf(10) fmt.Printf("type c :%T\n", c) // type c :reflect.Value } ``` ##### 通过反射设置变量的值 | 方法 | 说明 | | ------------------------- | ---------- | | reflect.Value.SetFloat() | 设置浮点数 | | reflect.value.SetInt() | 设置整数 | | reflect.Value.SetString() | 设置字符串 | 想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。而反射中使用专有的`Elem()`方法来获取指针对应的值。 **示例** ```go package main import ( "fmt" "reflect" ) func reflectSetValue1(x interface{}) { v := reflect.ValueOf(x) if v.Kind() == reflect.Int64 { v.SetInt(200) //修改的是副本,reflect包会引发panic } } func reflectSetValue2(x interface{}) { v := reflect.ValueOf(x) // 反射中使用 Elem()方法获取指针对应的值 if v.Elem().Kind() == reflect.Int64 { v.Elem().SetInt(200) } } func main() { var a int64 = 100 // reflectSetValue1(a) //panic: reflect: reflect.Value.SetInt using unaddressable value reflectSetValue2(&a) fmt.Println(a) //200 } ``` ##### 判断反射值的空和有效性 反射值对象(reflect.Value)提供一系列方法进行零值和空判定 | 方 法 | 说 明 | | -------------- | ------------------------------------------------------------ | | IsNil() bool | 返回值是否为 nil。如果值类型不是通道(channel)、函数、接口、map、指针或 切片时发生 panic,类似于语言层的v== nil操作 | | IsValid() bool | 判断值是否有效。 当值本身非法时,返回 false,例如 reflect Value不包含任何值,值为 nil 等。 | 下面的例子将会对各种方式的空指针进行 `IsNil()` 和 `IsValid()` 的返回值判定检测。同时对结构体成员及方法查找 map 键值对的返回值进行 `IsValid()` 判定。 ```go package main import ( "fmt" "reflect" ) func main() { // *int的空指针 var a *int fmt.Println("var a *int:", reflect.ValueOf(a).IsNil()) //var a *int: true // nil值 fmt.Println("nil:", reflect.ValueOf(nil).IsValid()) //nil: false // *int类型的空指针 fmt.Println("(*int)(nil):", reflect.ValueOf((*int)(nil)).Elem().IsValid()) //(*int)(nil): false // 实例化一个结构体 s := struct{}{} // 尝试从结构体中查找一个不存在的字段 fmt.Println("不存在的结构体成员:", reflect.ValueOf(s).FieldByName("").IsValid()) //不存在的结构体成员: false // 尝试从结构体中查找一个不存在的方法 fmt.Println("不存在的结构体方法:", reflect.ValueOf(s).MethodByName("").IsValid())//不存在的结构体方法: false // 实例化一个map m := map[int]int{} // 尝试从map中查找一个不存在的键 fmt.Println("不存在的键:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())//不存在的键: false } ``` ### 三、结构体反射 #### 3.1 与结构体相关的方法 任意值通过`reflect.TypeOf()`获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(`reflect.Type`)的`NumField()`和`Field()`方法获得结构体成员的详细信息。 `reflect.Type`中与获取结构体成员相关的的方法如下表所示。 | 方法 | 说明 | | :---------------------------------------------------------: | :----------------------------------------------------------: | | Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息。 | | NumField() int | 返回结构体成员字段数量。 | | FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息。 | | FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。 | | FieldByNameFunc(match func(string) bool) (StructField,bool) | 根据传入的匹配函数匹配需要的字段。 | | NumMethod() int | 返回该类型的方法集中方法的数目 | | Method(int) Method | 返回该类型方法集中的第i个方法 | | MethodByName(string)(Method, bool) | 根据方法名返回该类型方法集中的方法 | #### 3.2 结构体字段类型 `reflect.Type` 的 `Field()` 方法返回 `StructField` 结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(`StructTag`)等,而且还可以通过 `StructField` 的 Type 字段进一步获取结构体成员的类型信息。 **StructField 的结构如下:** ```go type StructField struct { Name string // 字段名 PkgPath string // 字段路径 Type Type // 字段反射类型对象 Tag StructTag // 字段的结构体标签 Offset uintptr // 字段在结构体中的相对偏移 Index []int // Type.FieldByIndex中的返回的索引值 Anonymous bool // 是否为匿名字段 } ``` #### 3.3 获取成员反射信息 下面代码中,实例化一个结构体并遍历其结构体成员,再通过 `reflect.Type` 的 `FieldByName()` 方法查找结构体中指定名称的字段,直接获取其类型信息。 **反射访问结构体成员类型及信息:** ```go package main import ( "fmt" "reflect" ) func main() { // 声明一个空结构体 type cat struct { Name string // 带有结构体tag的字段 Type int `json:"type" id:"100"` } // 创建cat的实例 ins := cat{Name: "mimi", Type: 1} // 获取结构体实例的反射类型对象 typeOfCat := reflect.TypeOf(ins) // 遍历结构体所有成员 for i := 0; i < typeOfCat.NumField(); i++ { // 获取每个成员的结构体字段类型 fieldType := typeOfCat.Field(i) // 输出成员名和tag fmt.Printf("name: %v tag: '%v'\n", fieldType.Name, fieldType.Tag) } // 通过字段名, 找到字段类型信息 if catType, ok := typeOfCat.FieldByName("Type"); ok { // 从tag中取出需要的tag fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id")) } } ``` **运行结果** ``` name: Name tag: '' name: Type tag: 'json:"type" id:"100"' type 100 ``` *附参考文章链接* *https://www.liwenzhou.com/posts/Go/13_reflect/#autoid-2-0-0* *http://c.biancheng.net/view/4407.html*
标签:
Golang基础
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://www.lilinchao.com/archives/2256.html
上一篇
25.Golang并发安全和锁
下一篇
27.Golang之反射三定律
取消回复
评论啦~
提交评论
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
标签云
FastDFS
Java工具类
Spark Streaming
Zookeeper
NIO
Eclipse
Kafka
Scala
队列
JavaSE
查找
Typora
锁
gorm
Hive
前端
MyBatis-Plus
工具
Thymeleaf
SpringBoot
散列
二叉树
MySQL
Ubuntu
Tomcat
Filter
SQL练习题
Elastisearch
Kibana
RSA加解密
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞