目录
前言
反射机制是运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,而不需要在编译时就知道这些变量的具体类型。跟Java一样,go也提供了反射功能。
其实之前就已经用过反射机制,如下语句输出变量的动态类型,底层就是用的reflect包实现的。
fmt.Printf("%T\n", i)
所有变量都可以由interface{}变量引用,很多函数或方法参数就是interface{},在函数内部通常需要获取interface{} x接口值的类型,通过判断其类型执行不同的处理逻辑。go接口文章中有谈到接口值包括一个具体的类型和那个类型的值,即动态类型和动态值,此处反射方法可以获取其动态类型和动态值。
- 1.反射可以从接口值到反射类型对象
- 2.反射可以从反射类型对象到接口值
- 3.修改反射类型变量的内部值需要保证其可设置性
1. 导出函数
函数 | 功能 |
---|---|
reflect.ValueOf(obj) | 返回obj的Value (如果要对obj进行修改的话传入变量地址) |
reflect.TypeOf(obj) | 返回obj的对象类型 |
reflect.Indirect(value) | 返回Value,获取了该指针指向的值 |
reflect.New(typ Type) | 返回一个新的type类型的对象 |
t := reflect.TypeOf(3) // 获取动态类型,返回reflect.Type
v := reflect.ValueOf(3) // 获取动态值,返回reflect.Value
reflect包包含一个接口Type和一个结构体Value常用于这种场景,可以通过这两个获取底层字段、调用方法、修改字段等等功能,能够实现Java反射机制的类似功能。Value组合了rtype,rtype实现了Type,所以Value包含了Type的所有方法。
reflect.Value能装载任意的值,还有很多方法来检查其内容,设置其内容。reflect.Value类型的变量和interface{}接口值可以互转。
reflect.Value v可以通过Interface() x方法返回接口值,接口值通过reflect.ValueOf(x)获取其Value。
Value的方法 | 功能 |
---|---|
Kind() Kind | 数据是什么类型:slice切面.Struct对象 |
Type() Type | 返回动态类型 |
Elem() Type | 元素类型 |
Interface() (i interface{}) | Value类型转interface{}接口值 |
CanAddr() | 能活获取地址 |
CanSet() bool | 返回值能否更改 |
Set(x Value) | 设置值 |
NumField() int | 结构体字段的个数 |
FieldByName("a") Value | 根据字段名称获取 |
Field(1) Value | 字段下标 |
NumMethod() int | 方法个数 |
Method(index int) Value | 第i个方法 |
MethodByName(name string) Value | 按照名称获取方法 |
Call(in []Value) []Value | 调用当前Value(必须是func类型),参数是in slice,返回[]Value |
2.常用操作
通常需要获取值、修改值、获取字段、修改字段、获取方法、调用方法。
2.1 获取&修改值Value
2.1.1 获取
// 获取值
c := reflect.ValueOf(&x) // &x *int no 获取指针的值Value
d := c.Elem() // 2 int yes (x)
fmt.Println(d.CanAddr()) // "true" 指针类型可取地址
2.1.2 修改
有一些reflect.Value是可取地址的,如reflect.ValueOf(&x).Elem()来获取任意变量x对应的可取地址的Value,修改Value有两种方式:
- 可以通过获取地址转换变量指针而修改值
- 通过调用Value的Set方法修改值
// 1.通过指针修改值
d := reflect.ValueOf(&x).Elem() // d 指向 x
px := d.Addr().Interface().(*int) // px := &x 转化成指向x的指针*int 涉及到Value到interface{}
*px = 3 // x = 3 // 指针
fmt.Println(x) // "3"
// 2.通过可取地址Value的set函数
d.Set(reflect.ValueOf(4))
fmt.Println(x) // "4"
修改值Value有如下限制:
- 可赋值性约束:将在运行时执行和编译时进行的可赋值性约束检查。赋值两边的类型不一致,那么程序将抛出一个panic异常;
- 可取地址:对一个不可取地址的reflect.Value调用Set方法也会导致panic异常;只有指针类型可以取地址
x := 2
b := reflect.ValueOf(x)
b.Set(reflect.ValueOf(3)) // panic: 不可取地址Value不能调用Set方法
2.2 获取结构体字段
类似于Java获取对象的字段
// 测试用结构体
type stut struct {
a int
b int
}
// 获取结构体字段
stru := stut{a:3,b:4} // 创建结构体实例
struv := reflect.ValueOf(&stru).Elem() // 指针
a := struv.FieldByName("a") // 获取结构体a字段
fmt.Printf("%T", a)
fmt.Println(a)
获取到字段后也是Value类型,所有的反射类型都是Value的,可以通过上一节的方式,修改字段的值。
2.3 获取&调用方法
类似于Java遍历类型的方法,还可以调用。go通过Value可以获取Method
v := reflect.ValueOf(x)
for i := 0; i < v.NumMethod(); i++ {
methType := v.Method(i).Type()
fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name,
strings.TrimPrefix(methType.String(), "func"))
}
使用reflect.Value.Call方法(我们之类没有演示),将可以调用一个Func类型的Value
ValueOf(p).Method(0).Call(nil) // 获取p类型的第0个方法,并调用,输入参数为空
2.4 获取Tags
类似于Java可以通过反射获取注解annotation,Go可以通过反射获取类型的tag,tag是什么呢?如下,tag就是`http:"1"`这样的,类似于Java注解的@tagxxx,reflect.StructTag类型就是对应这些tag。
tag格式:
- 由多个部分连接而成,空格分割:`form:"certificate_code" binding:"required,zh_idcard_no"`
- 部分格式为 key:value
- key是非空的字符串,由非控制字符组成,并且不可以是空格、双引号、冒号
- value由双引号包围,遵循Go字符串字面值语法
var data struct {
Labels []string `http:"l"`
MaxResults int `http:"max"`
Exact bool `http:"x"`
}
fieldInfo := v.Type().Field(i) // 获取结构体的字段
tag := fieldInfo.Tag // 获取结构体的tag
name := tag.Get("http") // 获取该字段 tag部分key为http的值
这个功能最常用地是定义vo类型,解析http请求等等。