通俗来讲就是, go 语言中提供一种机制,可以在代码运行时获取变量的类型和值,这种机制就是反射。
反射是由 reflect 包提供支持. 它定义了两个重要的类型, Type 和 Value. 一个 Type 表示一个Go类型. 函数 reflect.TypeOf 接受任意的 interface{} 类型, 并返回对应动态类型的reflect.Type.
reflect 包中另一个重要的类型是 Value. 一个 reflect.Value 可以持有一个任意类型的值. 函数 reflect.ValueOf 接受任意的 interface{} 类型, 并返回对应动态类型的reflect.Value. 和 reflect.TypeOf 类似, reflect.ValueOf 返回的结果也是对于具体的类型, 但是 reflect.Value 也可以持有一个接口值.
反射的使用场景:写测试用例的时候可以使用反射
其他场景尽量不要使用反射,原因如下:
1. 业务代码中写反射,会增加复杂性,让人难以理解
2. 反射性能比较低,比正常代码要慢一到两个数量级
3. 反射并不能在编译时检查出错误,在运行时可能会出现panic
所以在正常代码中尽量不要使用反射。
2.1 已知变量值,转化为对应的struct
- 示例如下:
package main
import (
"fmt"
"reflect"
)
type Addr struct {
Province string
City string
Telephone string
}
func main() {
addr := Addr {
Province: "HuNan",
City: "ShaoYang",
Telephone: "15511111111",
}
reflectValue := reflect.ValueOf(addr)
refectValueOfAddr, _ := reflectValue.Interface().(Addr)
fmt.Printf("Province: %s, City: %s, Phone: %s\n", refectValueOfAddr.Province, refectValueOfAddr.City, refectValueOfAddr.Telephone)
}
- TypeOf方法,ValueOf方法和FieldByName方法
type Addr struct {
Province string
City string
Telephone string
}
type Student struct {
Name string
Age int
Sex string
Address Addr
}
func testTypeOfAndValueOf() {
fmt.Println("--------------start testTypeOfAndValueOf----------------")
student := new(Student)
student.Name = "python"
fmt.Println(reflect.TypeOf(student))
var student2 Student
student2.Name = "golang"
fmt.Println(reflect.TypeOf(student2))
fmt.Println(reflect.ValueOf(student2).FieldByName("Name"))
fmt.Println("--------------end testTypeOfAndValueOf----------------")
}
- CanSet方法
func testCanSet() {
fmt.Println("--------------start testCanSet----------------")
var student Student
student.Name = "golang"
fmt.Println("reflect.ValueOf(student).FieldByName(\"Name\").CanSet()", reflect.ValueOf(student).FieldByName("Name").CanSet())
fmt.Println("reflect.ValueOf(&(student.Name)).Elem().CanSet()", reflect.ValueOf(&(student.Name)).Elem().CanSet())
c := "golang"
p := reflect.ValueOf(&c)
fmt.Println("p.CanSet() = ", p.CanSet())
fmt.Println("p.Elem().CanSet() = ", p.Elem().CanSet())
p.Elem().SetString("newName")
fmt.Println("c = ", c)
fmt.Println("--------------end testCanSet----------------")
}
2.2 type相关方法
package main
import (
"reflect"
"fmt"
)
type lx interface {
SayHi()
}
type User struct {
Name string
Age int64
Sex string
}
func (u *User) SayHi() {
fmt.Println("hello world")
}
func main() {
user := User{"张三", 25, "男"}
FillStruct(user)
}
func FillStruct(obj interface{}) {
t := reflect.TypeOf(obj) //反射出一个interface{}的类型
fmt.Println(t.Name()) //类型名
fmt.Println(t.Kind().String()) //Type类型表示的具体分类
fmt.Println(t.PkgPath()) //反射对象所在的短包名
fmt.Println(t.String()) //包名.类型名
fmt.Println(t.Size()) //要保存一个该类型要多少个字节
fmt.Println(t.Align()) //返回当从内存中申请一个该类型值时,会对齐的字节数
fmt.Println(t.FieldAlign()) //返回当该类型作为结构体的字段时,会对齐的字节数
var u User
fmt.Println(t.AssignableTo(reflect.TypeOf(u))) // 如果该类型的值可以直接赋值给u代表的类型,返回真
fmt.Println(t.ConvertibleTo(reflect.TypeOf(u))) // 如该类型的值可以转换为u代表的类型,返回真
fmt.Println(t.NumField()) // 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic
fmt.Println(t.Field(0).Name) // 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic
fmt.Println(t.FieldByName("Age")) // 返回该类型名为name的字段(会查找匿名字段及其子字段),布尔值说明是否找到,如非结构体将panic
fmt.Println(t.FieldByIndex([]int{0})) // 返回索引序列指定的嵌套字段的类型,等价于用索引中每个值链式调用本方法,如非结构体将会panic
}
2.3 value相关方法
package main
import (
"reflect"
"fmt"
)
type User struct {
Name string
Age int
Sex bool
Phone *string
Qian float64
Atest uint
Group interface{}
Btest interface{}
}
func (u *User) Hello() {
fmt.Println("hello world 你好世界")
}
func main() {
a := "hello world 你好世界"
user := &User{"张三", 25, true, &a, 88.8, 9, 99, nil}
var obj interface{} = user
v := reflect.ValueOf(obj)
method := v.MethodByName("Hello") //返回v的名为Hello的方法
method.Call([]reflect.Value{}) //执行反射的方法
fmt.Println(v.IsValid()) //返回v是否持有值,如果v是value零值会返回假,此时v除了IsValid String Kind之外的方法都会导致panic
fmt.Println(v.Kind()) //返回v持有值的类型,如果是结构体类型,返回 struct
fmt.Println(v.Type()) //返回v持有值的类型Type表示,返回具体定义类型, 例如User
v = v.Elem() //返回持有的接口的值,或者指针的值,如果不是interface{}或指针会panic,实际上是从 *User到User
var u User
fmt.Println(v.Convert(reflect.TypeOf(u)).FieldByName("Name")) //转换为其他类型的值,如果无法使用标准Go转换规则来转换,那么panic
fmt.Println(v.FieldByName("Name").CanSet()) //是否可以设置Name的值
v.FieldByName("Name").SetString("把Name值修改一下") //设置v的持有值,如果v的kind不是string或者v.Canset()返回假,会panic
v.FieldByName("Name").Set(reflect.ValueOf(a)) //将v的持有值修改为a的反射值,如果Canset返回假,会panic
fmt.Println(v.FieldByName("Group").Elem()) //返回持有的接口的值,或者指针的值,如果不是interface{}或指针会panic
fmt.Println(v.FieldByName("Phone").Elem()) //或者指针的值
fmt.Println(v.FieldByName("Name").Interface()) //把Name当做interface{}值
fmt.Println(v.FieldByName("Name").String()) //返回v持有的值的字符串表示,如果v的值不是string也不会panic
fmt.Println(v.FieldByName("Sex").Bool()) //返回持有的布尔值,如果v的kind不是bool会panic
fmt.Println(v.FieldByName("Age").Int()) //返回持有的int64,如果v的kind不是int int8-int64会panic
var x int64
fmt.Println(v.FieldByName("Age").OverflowInt(x)) //如果v持有值的类型不能无一出的表示x,会返回真,如果v的kind不是int int8-int64会panic
fmt.Println(v.FieldByName("Atest").Uint()) //返回v持有的无符号整数,如果v的kind不是uint uintptr uint8 uint16 uint32 uint64会panic
var x2 uint64
fmt.Println(v.FieldByName("Atest").OverflowUint(x2)) //如果v持有的值的类型不能无溢出的表示x2,会返回真,如果v的kind不是uint uintptr uint8 uint16 uint32 uint64会panic
fmt.Println(v.FieldByName("Qian").Float()) //返回v持有的浮点数float64,如果v的kind不是float32 float64会panic
var x3 float64
fmt.Println(v.FieldByName("Qian").OverflowFloat(x3)) //如果v持有值的类型不能无溢出的表示x3,会返回真,如果v的kind不是float32 float64会panic
fmt.Println(v.FieldByName("Btest").IsNil()) //如果v持有值是否为nil,如果v的值不是通道 函数 接口 映射 指针 切片之一会panic
fmt.Println(v.NumField()) //返回v持有的结构体类型值的字段数,如果v的kind不是struct会panic
fmt.Println(v.Field(0)) //返回结构体的第i个字段,如果v的kind不是struct或i出界会panic
fmt.Println(v.FieldByIndex([]int{0})) //和上面一样,没明白有啥用
}
3. Type和value方法汇总
3.1 Type和Value拥有的同名方法
Type | Value | 备注 |
---|---|---|
Kind | Kind | 表示 特定类型,go中定义的类型 ,如自己定义的 type Test struct {}, Kind 就是 struct |
MethodByName | MethodByName | 根据方法名找方法 |
Method | Method | 返回第i个方法 |
NumMethod | NumMethod | 返回拥有的方法总数,包括unexported方法 |
Field | Field | 取struct结构的第n个field |
FieldByIndex | FieldByIndex | 嵌套的方式取struct的field,比如v.FieldByIndex(1,2,3)等价于 v.field(1).field(2).field(3) |
FieldByNameFunc | FieldByNameFunc | 返回名称匹配match函数的field |
NumField | NumField | 返回struct所包含的field数量 |
3.2 Type独有的方法
方法名 | 备注 |
---|---|
Align | 分配内存时的内存对齐字节数 |
FieldAlign | 作为struct的field时内存对齐字节数 |
Name | type名 string类型 |
PkgPath | 包路径, "encoding/base64", 内置类型返回empty string |
Size | 该类型变量占用字节数 |
String | type的string表示方式 |
Implements | 判断该类型是否实现了某个接口 |
AssignableTo | 判断该类型能否赋值给某个类型 |
ConvertibleTo | 判断该类型能否转换为另外一种类型 |
Comparable | 判断该类型变量是否可以比较 |
ChanDir | 返回channel的方向 recv/send/double |
IsVariadic | 判断函数是否接受可变参数 |
Elem | 取该类型的元素 |
In | 函数第n个入参 |
Out | 函数第n个出参 |
NumIn | 函数的入参数个数 |
NumOut | 函数的出参个数 |
Key | 返回map结构的key类型Type |
Len | 返回array的长度 |
3.3 Value独有的方法
方法名 | 备注 |
---|---|
Addr | v的指针,前提时CanAddr()返回true |
Bool | bool类型变量的值 |
Bytes | []bytes类型的值 |
Call | 调用函数 |
CallSlice | 调用具有可变参的函数 |
CanAddr | 判断能否取址 |
CanInterface | 判断Interface方法能否使用 |
CanSet | 判断v的值能否改变 |
Cap | 判断容量 Array/Chan/Slice |
Close | 关闭Chan |
Complex | |
Convert | 返回将v转换位type t的结果 |
Elem | 返回interface包含的实际值 |
Float | |
Index | 索引操作 Array/Slice/String |
Int | |
Interface | 将当前value以interface{}形式返回 |
IsNil | 判断是否为nil,chan, func, interface, map, pointer, or slice value |
IsValid | 是否是可操作的Value,返回false表示为zero Value |
Len | 适用于Array, Chan, Map, Slice, or String |
MapIndex | 对map类型按key取值 |
MapKeys | map类型的所有key的列表 |
OverflowComplex | |
OverflowFloat | 溢出判断 |
OverflowInt | |
OverflowUint | |
Pointer | 返回uintptr 适用于slice |
Recv | chan接收 |
Send | chan发送 |
Set | 将x赋值给v,类型要匹配 |
SetBool | |
SetBytes | |
SetCap | slice调整切片 |
SetMapIndex | map赋值 |
SetUint | |
SetPointer | unsafe.Pointer赋值 |
SetString | |
Slice | return v[i:j] 适用于Array/Slict/String |
String | return value的string表示方法 |
TryRecv | chan非阻塞接收 |
Try Send | chan非阻塞发送 |
Type | 返回value的Type |
UnsafeAddr | 返回指向value的data的指针 |