李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
GO
正文
26.Golang之反射介绍
Leefs
2022-07-22 AM
1163℃
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://lilinchao.com/archives/2256.html
上一篇
25.Golang并发安全和锁
下一篇
27.Golang之反射三定律
评论已关闭
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
2
标签云
Netty
Java工具类
设计模式
ajax
Azkaban
Docker
Java编程思想
Shiro
HDFS
Java阻塞队列
Spark SQL
SpringCloudAlibaba
并发线程
Elasticsearch
随笔
正则表达式
国产数据库改造
SpringCloud
BurpSuite
FastDFS
ClickHouse
序列化和反序列化
前端
SQL练习题
排序
Hbase
Typora
Hive
链表
Elastisearch
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭