1. 反射的概念
在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。每种语言的反射模型都不同,并且有些语言根本不支持反射。Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用, 来源: https://juejin.im/post/6844903559335526407。
Golang 中一个变量包含两个部分组成:变量的值和变量的类型,类型有 int,string 等。在Golang 的一个interface变量,就是因为记录了这个变量原本的类型和值两个属性,所以才能用类型断言来区分类型。
反射是一把双刃剑,多存在与第三库或者框架中,比如 json和rpc 等,日常业务代码编写中很少使用反射,这个和反射的优缺点相关:
- 反射可以灵活的处理代码中变量
- 反射的性能很差
- 反射的代码比较脆弱,在运行时容易出现 panic 错误
- 反射的代码相对而言可读性比较差,不利于项目的维护
2. 反射中的TypeOf
Golang 反射中的TypeOf用于获取变量的类型信息,在TypeOf()得到的接口Type存在 Name() 和 Kind()两种类型,前者是具体的类型名称,后者kind为种类。如下面结构体student的实例e1,它的Kind为struct,name为Student。
需要注意的是,数组、切片、Map、指针等类型的变量的 Name() 为空字符串。
1. Type 接口
type Type interface {
// Kind返回该接口的具体分类
Kind() Kind
// Name返回该类型在自身包内的类型名,如果是未命名类型会返回""
Name() string
// 返回array类型的长度,如非数组类型将panic
Len() int
// 返回该类型的元素类型,如果该类型的Kind不是Array、Chan、Map、Ptr或Slice,会panic
Elem() Type
// 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic
NumField() int
// 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic
Field(i int) StructField
// 返回索引序列指定的嵌套字段的类型,
// 等价于用索引中每个值链式调用本方法,如非结构体将会panic
FieldByIndex(index []int) StructField
// 返回该类型名为name的字段(会查找匿名字段及其子字段),
// 布尔值说明是否找到,如非结构体将panic
FieldByName(name string) (StructField, bool)
// 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 如果函数类型的最后一个输入参数是"..."形式的参数,IsVariadic返回真
// 如果这样,t.In(t.NumIn() - 1)返回参数的隐式的实际类型(声明类型的切片)
// 如非函数类型将panic
IsVariadic() bool
// 返回func类型的参数个数,如果不是函数,将会panic
NumIn() int
// 返回该类型的方法集中方法的数目
// 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;
// 匿名字段导致的歧义方法会滤除
NumMethod() int
// 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic
// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
Method(int) Method
// 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法
// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
MethodByName(string) (Method, bool)
// 内含隐藏或非导出方法,以及部分不常用方法
}
2. TypeOf()
func TypeOf(i interface{}) Type
获取参数 i 的类型信息
3. Kind类型
type Kind uint
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
func getType(i interface{}) {
t := reflect.TypeOf(i)
fmt.Printf("value:%#v, typeof:%v, kind:%v, name:%v, type:%T\n", i, t, t.Kind(), t.Name(), t)
}
func main() {
a1 := "ssss"
getType(a1)
b1 := int8(120)
getType(b1)
c1 := []int{1, 2, 3}
getType(c1)
d1 := map[int]string{1: "a"}
getType(d1)
type student struct {
id string
name string
}
e1 := student{"s1001", "ZhangSan"}
getType(e1)
getType(&e1)
}
[root@duduniao go_learn]# go run day24/reflect/type/main.go
value:"ssss", typeof:string, kind:string, name:string, type:*reflect.rtype
value:120, typeof:int8, kind:int8, name:int8, type:*reflect.rtype
value:[]int{1, 2, 3}, typeof:[]int, kind:slice, name:, type:*reflect.rtype
value:map[int]string{1:"a"}, typeof:map[int]string, kind:map, name:, type:*reflect.rtype
value:main.student{id:"s1001", name:"ZhangSan"}, typeof:main.student, kind:struct, name:student, type:*reflect.rtype
value:&main.student{id:"s1001", name:"ZhangSan"}, typeof:*main.student, kind:ptr, name:, type:*reflect.rtype
3. 反射中的ValueOf
使用 ValueOf() 可以获取到对应的值结构体,并可以通过该结构体方法获取到需要内容。
1. Value
type Value struct {
// 内含隐藏或非导出字段
}
2. ValueOf()
func ValueOf(i interface{}) Value
获取变量 i 的值信息,该信息存储在 Value 的结构体实例中
3. Kind()
func (v Value) Kind() Kind
Kind返回v持有的值的分类,如果v是Value零值,返回值为Invalid
4. Type()
func (v Value) Type() Type
返回v持有的值的类型的Type表示。
5. Elem()
func (v Value) Elem() Value
Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。
如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。
6. 转换为go中具体类型
func (v Value) Bool() bool
func (v Value) Int() int64
func (v Value) Uint() uint64
func (v Value) Float() float64
func (v Value) Pointer() uintptr
func (v Value) Bytes() []byte
func (v Value) String() string
func (v Value) Interface() (i interface{})
7. 判断
func (v Value) IsValid() bool
IsValid返回v是否持有一个值。如果v是Value零值会返回假,此时v除了IsValid、String、Kind之外的方法都会导致panic。
绝大多数函数和方法都永远不返回Value零值。如果某个函数/方法返回了非法的Value,它的文档必须显式的说明具体情况。
func (v Value) IsNil() bool
IsNil报告v持有的值是否为nil。v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic。
注意IsNil并不总是等价于go语言中值与nil的常规比较。
例如:如果v是通过使用某个值为nil的接口调用ValueOf函数创建的,v.IsNil()返回真,但是如果v是Value零值,会panic。
3.1. 通过反射获取值
func getValue(i interface{}) {
v := reflect.ValueOf(i)
switch v.Kind() {
case reflect.Int:
fmt.Printf("type: int, value:%d, valueType: %T\n", int(v.Int()), v)
case reflect.String:
fmt.Printf("type: string, value:%s, valueType: %T\n", v.String(), v)
}
}
func main() {
a2 := 123456
getValue(a2)
b2 := "Hello world"
getValue(b2)
}
3.2. 通过反射设置值
通过函数修改值,必须要传入参数得指针,否则无法修改成功。
func setValue(i interface{}) {
v := reflect.ValueOf(i)
if v.Elem().Kind() == reflect.Int {
v.Elem().Set(reflect.ValueOf(1024))
}
}
func main() {
a2 := 1000
setValue(&a2)
fmt.Printf("type:%T, value:%d\n", a2, a2)
}
4. 结构体类型的反射
结构体类型在反射中用的比较多,比如解析 ini 文件,解析 json ,gorm 操作等。一个是获取结构体中得字段信息,一个是获取结构体方法信息。
4.1. 获取结构体字段信息
1. StructField
type StructField struct {
// Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。
Name string
PkgPath string
Type Type // 字段的类型
Tag StructTag // 字段的标签
Offset uintptr // 字段在结构体中的字节偏移量
Index []int // 用于Type.FieldByIndex时的索引切片
Anonymous bool // 是否匿名字段
}
2. 在TypeOf()获取得Type类型具备以下结构体相关方法
// 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic
NumField() int
// 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic
Field(i int) StructField
// 返回索引序列指定的嵌套字段的类型,
// 等价于用索引中每个值链式调用本方法,如非结构体将会panic
FieldByIndex(index []int) StructField
// 返回该类型名为name的字段(会查找匿名字段及其子字段),
// 布尔值说明是否找到,如非结构体将panic
FieldByName(name string) (StructField, bool)
// 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic
FieldByNameFunc(match func(string) bool) (StructField, bool)
3. Value 类型包含了以下与结构体相关得方法
(1) NumField()
func (v Value) NumField() int
返回v持有的结构体类型值的字段数,如果v的Kind不是Struct会panic
(2) Field()
func (v Value) Field(i int) Value
返回结构体的第i个字段(的Value封装)。如果v的Kind不是Struct或i出界会panic
(3) FieldByIndex()
func (v Value) FieldByIndex(index []int) Value
返回索引序列指定的嵌套字段的Value表示,等价于用索引中的值链式调用本方法,如v的Kind非Struct将会panic
(4) FieldByName()
func (v Value) FieldByName(name string) Value
返回该类型名为name的字段(的Value封装)(会查找匿名字段及其子字段),如果v的Kind不是Struct会panic;如果未找到会返回Value零值。
(5) FieldByNameFunc()
func (v Value) FieldByNameFunc(match func(string) bool) Value
返回该类型第一个字段名满足match的字段(的Value封装)(会查找匿名字段及其子字段),如果v的Kind不是Struct会panic;如果未找到会返回Value零值。
4. 获取tag中字段
func (tag StructTag) Get(key string) string
type Host struct {
UUID string `json:"uuid"`
IP string `json:"ip"`
User string `json:"user"`
Password string `json:"password"`
}
func getFieldByType(s interface{}) {
t := reflect.TypeOf(s)
if t.Name() != "Host" {
fmt.Printf("Type error, need Host,recv %s\n", t.Name())
return
}
// 遍历所有字段
for i := 0; i < t.NumField(); i++ {
fmt.Printf("file_name:%s, tag:%#v, json:%s\n", t.Field(i).Name, t.Field(i).Tag, t.Field(i).Tag.Get("json"))
}
// 根据字段名找字段信息
if name, ok := t.FieldByName("IP"); ok {
fmt.Printf("file_name:%s, tag:%#v\n", name.Name, name.Tag)
}
}
func getFieldByValue(s interface{}) {
v := reflect.ValueOf(s)
if v.Kind() != reflect.Struct {
fmt.Printf("Type error, need Host,recv %s\n", v.Kind().String())
return
}
// 遍历所有字段
for i := 0; i < v.NumField(); i++ {
fmt.Printf("value:%v\n", v.Field(i).String())
}
// 根据字段名找字段信息
fmt.Printf("value:%s\n", v.FieldByName("IP").String())
}
func main() {
ubuntu007010 := Host{
UUID: "6fae9e37-2cc0-41a6-8e43-f1201def9ecc",
IP: "10.4.7.10",
User: "root",
Password: "123456",
}
getFieldByType(ubuntu007010)
getFieldByValue(ubuntu007010)
}
[root@duduniao go_learn]# go run day24/reflect/struct/main.go
file_name:UUID, tag:"json:\"uuid\"", json:uuid
file_name:IP, tag:"json:\"ip\"", json:ip
file_name:User, tag:"json:\"user\"", json:user
file_name:Password, tag:"json:\"password\"", json:password
file_name:IP, tag:"json:\"ip\""
value:6fae9e37-2cc0-41a6-8e43-f1201def9ecc
value:10.4.7.10
value:root
value:123456
value:10.4.7.10
4.2. 获取结构体方法
1. 在TypeOf()获取得Type类型具备以下结构体相关方法
// 返回该类型的方法集中方法的数目
// 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;
// 匿名字段导致的歧义方法会滤除
NumMethod() int
// 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic
// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
Method(int) Method
// 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法
// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
MethodByName(string) (Method, bool)
2. Value 类型包含了以下与结构体相关得方法
(1) NumMethod()
func (v Value) NumMethod() int
返回v持有值的方法集的方法数目。
(2) Method()
func (v Value) Method(i int) Value
返回v持有值类型的第i个方法的已绑定(到v的持有值的)状态的函数形式的Value封装。
返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。
如果i出界,或者v的持有值是接口类型的零值(nil),会panic。
(3) MethodByName()
func (v Value) MethodByName(name string) Value
返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装。
返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。
如果未找到该方法,会返回一个Value零值
(4) 方法调用
func (v Value) Call(in []Value) []Value
通过Call调用方法时,参数必须是 []Value 切片
type Host struct {
UUID string `json:"uuid"`
IP string `json:"ip"`
User string `json:"user"`
Password string `json:"password"`
}
func (h Host) Info() {
if marshal, err := json.Marshal(h); err != nil {
fmt.Println(marshal)
}
}
func (h Host) InitHost(tool string) {
fmt.Printf("Init host %s by tool:%s\n", h.UUID, tool)
}
func getMethodByType(s interface{}) {
typeOf := reflect.TypeOf(s)
if typeOf.Kind() != reflect.Struct {
return
}
// 遍历method, 如果是指针接收者方法,此处会有异常
for i := 0; i < typeOf.NumMethod(); i++ {
name := typeOf.Method(i).Name
t := typeOf.Method(i).Type.String()
fmt.Printf("method name:%s, type:%#v\n", name, t)
}
// 根据方法名称获取方法
if fun, ok := typeOf.MethodByName("Info"); ok {
fmt.Printf("method name:%s, type:%#v\n", fun.Name, fun.Type.Name())
}
}
func getMethodByValue(s interface{}) {
value := reflect.ValueOf(s)
if value.Kind() != reflect.Struct {
return
}
args := []reflect.Value{reflect.ValueOf("ansible")}
value.MethodByName("InitHost").Call(args)
}
func main() {
ubuntu007010 := Host{
UUID: "6fae9e37-2cc0-41a6-8e43-f1201def9ecc",
IP: "10.4.7.10",
User: "root",
Password: "123456",
}
getMethodByType(ubuntu007010)
getMethodByValue(ubuntu007010)
}
[root@duduniao go_learn]# go run day24/reflect/struct/main.go
method name:Info, type:"func(main.Host)"
method name:InitHost, type:"func(main.Host, string)"
method name:Info, type:""
Init host 6fae9e37-2cc0-41a6-8e43-f1201def9ecc by tool:ansible