Go语言反射介绍
反射是什么?
反射概念
反射本身是物理名词, 如光的反射,声音的反射,该文章介绍的反射是计算机编程语言的反射,根据维基百科的定义是指程序运行期间,可以反问,检测和修改本身状态的一种能力。比喻的来说:反射就是成勋运行的时候能“观察”并且修改自己的行为的一种能力
我的理解: 在程序运行时期对程序本身进行访问和修改,即只有在编译过程中才能查看类型或者修改类型,确定变量类型。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
适用场景
不能预先知道函数参数类型,或者参数类型有很多种,无法用同一个类型来表示
函数需要根据入参来动态的执行不同的行为
Go程序在运行期使用reflect包访问程序的反射信息。
反射的优缺点
优点
可以在一定程序上避免硬编码,提供灵活性和通用性
可以作为一个第一个类对象发现并修改源代码的结构(如代码块,类,方法,协议等)
缺点
由于将部分类型检查工作从编译期推迟到了运行时,使得一些隐藏的问题无法通过编译期发现,提高了代码出现bug的几率,搞不好就会panic
反射出变量的类型需要额外的开销,降低了代码的运行效率
反射的概念和语法比较抽象,过多的使用反射,使得代码难以被其他人读懂,不利于合作与交流
其它语言中的反射
C/C++ 语言没有支持反射功能,只能通过 typeid 提供非常弱化的程序运行时类型信息。Java、C# 等语言都支持完整的反射功能。
Lua、JavaScript 类动态语言,由于其本身的语法特性就可以让代码在运行期访问程序自身的值和类型信息,因此不需要反射系统。
Go 程序的反射系统无法获取到一个可执行文件空间中或者是一个包中的所有类型信息,需要配合使用标准库中对应的词法、语法解析器和抽象语法树(AST)对源码进行扫描后获得这些信息。
Golang反射的原理是什么?
Golang反射的基本原理
Golang反射是通过接口来实现的,通过隐式转换,普通的类型被转换成interface类型,这个过程涉及到类型转换的过程,首先从Golang类型转为interface类型, 再从interface类型转换成反射类型, 再从反射类型得到想的类型和值的信息.
总体流程
Go语言中的反射 relfect
reflect 包
Go语言中的反射是由 reflect 包提供支持的,它定义了两个重要的类型 Type 和 Value 任意接口值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两部分组成,并且 reflect 包提供了 reflect.TypeOf 和 reflect.ValueOf 两个函数来获取任意对象的 Value 和 Type。
反射的类型对象(reflect.Type)
package main
import (
"fmt"
"reflect"
)
type Info struct {
Name string
}
func main() {
var a int
typeOfA := reflect.TypeOf(a)
fmt.Println(typeOfA.Name(), typeOfA.Kind())
c := Info{
Name: "thom",
}
typeOfC := reflect.TypeOf(c)
typeOfCPtr := reflect.TypeOf(&c)
fmt.Println(typeOfC.Name()," kind :", typeOfC.Kind())
fmt.Println(typeOfCPtr.Name()," kind :", typeOfCPtr.Kind())
}
反射的类型(Type)与种类(Kind)
在使用反射时,需要首先理解类型(Type)和种类(Kind)的区别。编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。例如需要统一判断类型中的指针时,使用种类(Kind)信息就较为方便。
1) 反射种类(Kind)的定义
Go语言程序中的类型(Type)指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型。
种类(Kind)指的是对象归属的品种,在 reflect 包中有如下定义:
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"
)
// 定义一个Enum类型
type Enum int
const (
Zero Enum = 0
)
func main() {
// 声明一个空结构体
type cat struct {
}
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(cat{})
// 显示反射类型对象的名称和种类
fmt.Println(typeOfCat.Name(), typeOfCat.Kind())
// 获取Zero常量的反射类型对象
typeOfA := reflect.TypeOf(Zero)
// 显示反射类型对象的名称和种类
fmt.Println(typeOfA.Name(), typeOfA.Kind())
}```
### 指针与指针指向的元素
Go语言程序中对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作,代码如下:
```go
package main
import (
"fmt"
"reflect"
)
func main() {
// 声明一个空结构体
type cat struct {
}
// 创建cat的实例
ins := &cat{}
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(ins)
// 显示反射类型对象的名称和种类
fmt.Printf("name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())
// 取类型的元素
typeOfCat = typeOfCat.Elem()
// 显示反射类型对象的名称和种类
fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())
}
运行结果
name:'' kind:'ptr'
element name: 'cat', element kind: 'struct'
使用反射获取结构体的成员类型
任意值通过 reflect.TypeOf() 获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象 reflect.Type 的 NumField() 和 Field() 方法获得结构体成员的详细信息。
- 结构体字段类型
reflect.Type 的 Field() 方法返回 StructField 结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(StructTag)等,而且还可以通过 StructField 的 Type 字段进一步获取结构体成员的类型信息。
type StructField struct {
Name string // 字段名
PkgPath string // 字段路径
Type Type // 字段反射类型对象
Tag StructTag // 字段的结构体标签
Offset uintptr // 字段在结构体中的相对偏移
Index []int // Type.FieldByIndex中的返回的索引值
Anonymous bool // 是否为匿名字段
}
- 获取成员反射信息
下面代码中,实例化一个结构体并遍历其结构体成员,再通过 reflect.Type 的 FieldByName() 方法查找结构体中指定名称的字段,直接获取其类型信息。
反射访问结构体成员类型及信息:
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
反射怎么用?
类型推断
encoding/json marshal方法
fmt. Printf
各种orm工具
例子
json序列化
func (e *encodeState) marshal(v interface{}, opts encOpts) (err error) {
defer func() {
if r := recover(); r != nil {
if je, ok := r.(jsonError); ok {
err = je.error
} else {
panic(r)
}
}
}()
e.reflectValue(reflect.ValueOf(v), opts)
return nil
}```
## 指针的判断赋值
根据传入的interface类型来填充这些类型的值
```go
func main() {
var interface1 interface{}
a := map[string]A{
"1": {
A: "1",
B: "1",
}
}
t = a
vr := reflect.Valueof(t)
if vr.Type().Kind() == reflect.map {
iter := vr.MapRange()
newM := reflect.MakeMap(vr.Type())
for iter.Next() {
k, v := iter.Key(), iter.Valie()
if k.String()=== "1" {
newM.SetMapIndex(k,v)
}
}
}
}
总结
反射是在程序运行的过程中通过某种机制来实现自己行为的描述和监测,Go语言的房舍通过reflect包来实现,因为类型是通过运行期间推动出来的,一般会消耗系统性能,一般使用反射是为了考虑程序的灵活性和通用性
参考文档
- http://c.biancheng.net/view/4407.html
- https://juejin.cn/post/6844903559335526407
- https://go.dev/blog/laws-of-reflection
- https://zhuanlan.zhihu.com/p/382424874