反射
Go 提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,但是在编译时并不知道这些变量的具体类型。
用途:
- 设计一类并不满足普通公共接口的类型的值
- 它们没有明确的表示方式
- 设计该函数时这些类型还不存在
reflect.Type和reflect.Value
reflect.Type和reflect.TypeOf()
函数reflect.TypeOf接收任意的interface{}类型
,并返回对应动态类型的reflect.Type
:
t := reflect.TypeOf(3) // 得到一个reflect.Type: 动态类型接口值,返回具体的类型
fmt.Println(t.String()) // "int"
fmt.Println(t) // 与上面一样"int"
由于打印动态类型值对于调试和日志时有帮助的,所以为fmt.Printf()提供了一个简单的%T标志参数,内部使用reflect.TypeOf的结果输出。
fmt.Printf("%T\n", 3) // "int"
reflect.Value和reflect.ValueOf()
一个reflect.Value
可以持有一个任意类型的值
,reflect.ValueOf函数接受任意的interface{}类型
,并返回对应动态类型的reflect.Value.
v := reflect.ValueOf(3) // 得到一个reflect.Value
fmt.Println(v) // "3"
fmt.Printf("%v\n", v) // "3"
fmt.Println(v.String()) // NOTE: "<int value>"
reflect.Value满足fmt.Stringer接口,使用fmt包的标志%v参数,可以将reflect.Value格式化
t := v.Type(). // reflect.Type
fmt.Println(t.String()) // "int"
逆操作
逆操作时调用reflect.ValueOf对应的reflect.Value.Interface方法,它返回一个interface类型,表示reflect.Value对应类型的具体值
v := reflect.ValueOf(3) // 一个接口值
x := v.Interface() // 一个interface{}
i := x.(int) // an int
fmt.Printf("%d\n", i) // "3"
通过reflect.Value修改值
有一些reflect.Value是可取地址的,我们可以调用reflect.Value的CanAddr方法判断其是否可以背取地址
x := 2
a := reflect.ValueOf(2) // 2的拷贝
b := reflect.ValueOf(x)
c := reflect.ValueOf(&x) // &x的拷贝
d := c.Elem() // 解引用方式生成
fmt.Println(a.CanAddr()) // "false"
...
fmt.Println(c.CanAddr()). // "true"
- 所有通过reflect.ValueOf(x)返回的reflect.Value都是不可取地址的
- 通过间接获取的reflect.Value都是可取地址的,即使开始是一个不可取地址的Value
通过可取地址的reflect.Value访问变量:
- 调用Addr(),返回Value,里面包括指向变量的指针。
- 在Value上调用Interface()方法,返回interface{},里面包括指向变量的指针。
- 使用类型的断言得到interface类型的接口强制转换为普通的指针
x := 2
d := reflect.ValueOf(&x).Elem() // 可取地址的Value
px := d.Addr().Interface().(*int) // px := &x
*px = 3 // x = 3
fmt.Println(x) // "3"
不使用指针
,通过调用可取地址的reflect.Value的reflect.Set方法来更新:
d.Set(reflect.ValueOf(4))
fmt.Println(x) // "4"
报错的情况
d.Set(reflect.ValueOf(int64(5))) // panic:int64 is not assignable to int 对应类型不匹配
x := 2
b := reflect.ValueOf(x)
b.Set(reflect.ValueOf(3) // panic: Set using unaddressable value
获取结构体字段标识
对于一个Web服务,大部分Http请求需要将请求中的参数保存到本地变量中,下面示例通过params.Unpack,通过使用结构体成员标签让Http处理函数解析请求参数。
总结
import (
"fmt"
"reflect"
)
type S struct {
Name string `Name:"name"`
Id int `Id:"ID"`
}
func main(){
s := S{
Name: "emmmmm...",
Id: 1,
}
...
}
使用reflect一般分成三步:
- 要去反射的是一个类型的值(这些值都实现了空interface),首先需要把它转化成reflect对象(reflect.Type或者reflect.Value【根据不同的情况调用不同的函数】)
// *reflect.rtype
// 需要传如可取地址而不是拷贝值
t := reflect.TypeOf(&s) // 得到类型的元数据,通过t可以取得类型定义里面的所有元素
// reflect.Value
// 需要传入可取地址而不是拷贝值
v := reflect.ValueOf(&s) // 得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
/*
main.S --类型信息
{emmmmm... 1} --值信息
*/
- 转化为reflect对象之后,可以进行进一步操作,也就是将reflect对象转化为相应的值
tag := t.Elem().Filed(0).Tag // 获取定义在struct里面的标签
name := v.Elem().Filed(0).String // 获取存储在第一个字段里面的值
/*
tag: Name:"name"
name: emmmm...
*/
- 通过
返回值
能返回相应的类型
和数值
var x float64 = 3.4
v := reflect.ValueOf(&x)
fmt.Println("type", v.Elem().Type())
fmt.Println("kind is float64:", v.Elem().Kind() == reflect.Float64)
fmt.Println("value:", v.Elem().Float())
/*
type: float64
kind is float64: true
value: 3.4
*/
通过反射修改某个值
var x float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(7.1)
/*
x: 7.1
*/
全部代码:
package main
import (
"fmt"
"reflect"
)
type S struct {
Name string `Name:"name"`
Id int `Id:"ID"`
}
func main(){
s := S{
Name: "emmmmm...",
Id: 1,
}
// 需要传可取地址
t := reflect.TypeOf(&s)
v := reflect.ValueOf(&s)
fmt.Println(t)
fmt.Println(reflect.TypeOf(t))
fmt.Println(v)
fmt.Println(reflect.TypeOf(v))
tag := t.Elem().Field(0).Tag
name := v.Elem().Field(0).String()
fmt.Printf("Tag: %s, value: %s \n", tag, name)
var x float64 = 3.4
vv := reflect.ValueOf(&x)
// 返回reflect.Value的类型
fmt.Println("type:", vv.Elem().Type())
// 对类型进行推断
fmt.Println("kind is float64:", vv.Elem().Kind() == reflect.Float64)
// 获取相应的值
fmt.Println("value:", vv.Elem().Float())
Ev := vv.Elem()
Ev.SetFloat(7.1)
fmt.Println("x:", x)
}