go 反射
go 反射
1.1 反射介绍
反射就是在运行期间(不是编译期间)探知对象的类型信息和内存结构、更新变量、调用它们的方法
1.1.1 反射的使用场景
- 函数的参数类型是interface{},需要在运行时对原始类型进行判断,针对不同的类型采取不同的处理方式。比如json.Marshal(v interface{})。
- 在运行时根据某些条件动态决定调用哪个函数,比如根据配置文件执行相应的算子函数
1.2 反射弊端
- 代码难以阅读,难以维护。
- 编译期间不能发现类型错误,覆盖测试难度很大,有些bug需要到线上运行很长时间才能发现,可能造成严重用后果。
- 反射性能很差,通常比正常代码慢一到两个数量级。在对性能要求很高,或大量反复调用的代码块里建议不要使用反射。
1.3 反射基础类型
1.3.1 reflect.Type
reflect.Type用于获取类型相关的信息
type Type interface {
Method(int) Method //第i个方法
MethodByName(string) (Method, bool) //根据名称获取方法
NumMethod() int //方法的个数
Name() string //获取结构体名称
PkgPath() string //包路径
Size() uintptr //占用内存的大小
String() string //获取字符串表述
Kind() Kind //数据类型
Implements(u Type) bool //判断是否实现了某接口
AssignableTo(u Type) bool //能否赋给另外一种类型
ConvertibleTo(u Type) bool //能否转换为另外一种类型
Elem() Type //解析指针
Field(i int) StructField //第i个成员
FieldByIndex(index []int) StructField //根据index路径获取嵌套成员
FieldByName(name string) (StructField, bool) //根据名称获取成员
FieldByNameFunc(match func(string) bool) (StructField, bool) //
Len() int //容器的长度
NumIn() int //输出参数的个数
NumOut() int //返回参数的个数
}
1.4 反射类型
1.4.1 reflect.TypeOf
1.4.1.1 Kind && String 案例
func fansheinit1() {
typeI := reflect.TypeOf(1)
typeS := reflect.TypeOf("hello")
fmt.Println(typeI)
fmt.Println(typeS)
fmt.Println(typeI.String())
fmt.Println(typeS.String())
fmt.Println(typeI.Kind() == reflect.Int)
fmt.Println(typeS.Kind() == reflect.String)
}
1.4.1.2 Elem && Name && PkgPath && Size
panic是go中很致命的一个报错,出现panic会直接退出
type test1 struct {
name string //16B
age uint64 //和int一样占 8B
sex byte //占1B 虽然是1B但是还会占8B
}
func main() {
u1 := test1{}
typeu1 := reflect.TypeOf(u1)
fmt.Println(typeu1) //这里打印main.test1
fmt.Println(typeu1.Kind()) //打印struct结构体类型
//u2 := &User{} //这里获取test1的指针,和下面 u2 := new(test1) 相同
u2 := new(test1)
typeu2 := reflect.TypeOf(u2)
fmt.Println(typeu2) //这里打印 *main.test1
fmt.Println(typeu2.Kind()) //打印Ptr指针类型
fmt.Println(typeu2.Kind() == reflect.Ptr ) //判断是否是指针类型,
结果为true
typeu3 := typeu2.Elem()
fmt.Println(typeu3) //这里打印main.test1
fmt.Println(typeu3.Kind()) //打印struct结构体类型,发现了没,和typeu1一样
fmt.Println(typeu1.Name()) //Name获取结构体名称,test1
fmt.Println(typeu1.PkgPath()) //包路径 结果是main
fmt.Println(typeu1.Size()) //占用内存的大小 结果是32B
}
1.4.1.3 NumField && Field
type fshst struct { //再定义一个结构体
name string
sex int `where:"beijing"`
age int `json:"xml" type:"huojimian"`
}
func main() {
typeUser := reflect.TypeOf(fshst{}) //reflect获取 fshst这个结构体的Typeof
filedNum := typeUser.NumField() //filedNum 获取成员变量个数
for i := 0;i < filedNum;i++ { //field根据成员变量个数遍历循环typeUser的结构体内容,name和age
field1 := typeUser.Field(i)
fmt.Printf("%d,%s Anonymous %t,Offset %d,Type %s,IsExported %t,Json %s\n",i,field1.Name, //filed1.Name 获取名称
field1.Anonymous, //field1.Anonymous 是否匿名
field1.Offset, //field1.Offset 偏移量
field1.Type, //field1.Type 获取类型
field1.IsExported(), //field1.IsExported() 是否可导出
field1.Tag.Get("where"), //field1.Tag.Get("where") 获取当前结构体的成员变量是否有json where,没有就是空
field1.Tag.Get("json"),
field1.Tag.Get("type"),
)
}
}
1.4.2 reflect.ValueOf
reflect.Value获取、修改原始数据类型里的值.
type Value struct {
// 代表的数据类型
typ *rtype
// 指向原始数据的指针
ptr unsafe.Pointer
}
1.4.2.1 type用于type和value之间的互相转化 Elem && Addr
type fshsinit5 struct {
name string
age int
}
func main() {
aValue := reflect.ValueOf("a") //用ValueOf,而不是TypeOf
bValue := reflect.ValueOf(1)
cValue := reflect.ValueOf(fshsinit5{ //调用fshsinit5结构体
name: "zcy",
age: 12,
})
fmt.Println(aValue)
fmt.Println(bValue)
fmt.Println(cValue)
dType := aValue.Type() //用Type,把ValueOf读取到转换成Type,形同于reflect.TypeOf
eType := bValue.Type()
fType := cValue.Type()
fmt.Println(dType.Kind(),aValue.Kind(),aValue.Kind() == dType.Kind()) //查看ValueOf的kind类型和TypeOf的kind类型是否一致
fmt.Println(eType.Kind(),bValue.Kind(),aValue.Kind() == eType.Kind()) //TODO:这里不知道为什么都是int,但是判断是否相等时是false
fmt.Println(fType.Kind(),cValue.Kind(),aValue.Kind() == fType.Kind())
gAddr := cValue.Elem() //Elem 和 Addr是互逆操作。Elem是解析指针
fmt.Println(gAddr.Kind() == reflect.Struct) //判断是否为结构体类型
hElem := gAddr.Addr()
fmt.Println(hElem.Kind() == reflect.Ptr) //判断是否为指针
//aValue.Interface().(string) 这一操作是通过把value,通过interface转换成interfcae,再转换成string。它和avalue.String()是相同作用,都是转换成string,可以结合那个反射的图看一下
fmt.Printf("origin value of avalue is %d %d\n",aValue.Interface().(string),aValue.String())
//这里和上面是类似的,bValue.Interface().(int)是断言类型是int
fmt.Printf("origin value of avalue is %d %d\n",bValue.Interface().(int),bValue.Int())
}
1.4.2.2 reflect.Invalid的 IsVaild && IsNil && IsZero
func main() {
fansheinit6()
}
func fansheinit6() {
var fshsinit6 interface{}
value1 := reflect.ValueOf(fshsinit6)
// 判断一个接口是否持有值
fmt.Printf("value1 持有的真实得到值是%t Isvalid是%t\n",value1.IsValid(),value1.Kind()==reflect.Invalid)
fshsinit6 = 12
value2 := reflect.ValueOf(fshsinit6)
fmt.Printf("value2 持有的真实得到值是%t Isvalid是%t\n",value2.IsValid(),value2.Kind()==reflect.Invalid)
var user *User = nil //定义一个user的持有值是nil
v := reflect.ValueOf(user)
if v.IsValid() { //IsValid判断v是否有持有值
fmt.Printf("v 持有的值是nil\n",v.IsNil()) //IsNil判断持有值是否为nil,IsNil之前必须保证IsVaild是true,贸然调用IsNil会报错。
} else {
fmt.Printf("v 没有持有值")
}
if v.IsValid() { //IsValid判断v是否有持有值
fmt.Printf("v 持有的值是0\n",v.IsZero()) //IsZero判断持有值是否为0
} else {
fmt.Printf("v 没有持有值")
}
}
1.4.3 可寻址
func fansheinit7() {
v1 := reflect.ValueOf(1)
var x int
v2 := reflect.ValueOf(x)
v3 := reflect.ValueOf(&x)
v4 := v3.Elem()
fmt.Printf("v1是可寻址%t\n", v1.CanAddr()) //false
fmt.Printf("v2是可寻址%t\n", v2.CanAddr()) //false
fmt.Printf("v3是可寻址%t\n", v3.CanAddr()) //false
fmt.Printf("v4是可寻址%t\n", v4.CanAddr()) //true
slice := make([]int, 3, 5)
v5 := reflect.ValueOf(slice)
v6 := v5.Index(2)
fmt.Printf("v5是可寻址%t\n",v5.CanAddr()) //切片的value不可寻址
fmt.Printf("v6是可寻址%t\n",v6.CanAddr()) //切片中的某个元素value可寻址
mp := make(map[int]bool, 5)
v7 := reflect.ValueOf(mp)
fmt.Printf("v7是可寻址%t\n",v7.CanAddr()) //map的value不可寻址
}
1.4.4 通过反射修改int && string && map && 切片
通过反射修改value的值 SetInt && SetString && FieldByName
如果是不可寻址的,找不到地址就无法修改value的值
如果是可寻址的,可以通过reflect.ValueOf修改value的值
func fansheinit8() {
var i int = 10 //指针修改int
iValue := reflect.ValueOf(i) //读取iValue
if iValue.CanAddr() { //判断是否是可寻址
iValue.SetInt(8) //如果是可寻址,把value改成8
fmt.Printf("iValue = %d\n",i) //打印i是否变成8,这里可惜,他不是可寻址
} else {
xValue := reflect.ValueOf(&i) //这里我们让xValue获取 &i的的指针
yValue := xValue.Elem() //,然后yValue解析xValue的指针,yValue是可寻址的
fmt.Printf("yValue是可寻址的%t\n",yValue.CanAddr())
yValue.SetInt(12)
fmt.Printf("可寻址的yValue修改value值后是%d\n",yValue)
}
var s string = "hello" //指针修改string
sValue := reflect.ValueOf(&s) //先获取指针
sValue.Elem().SetString("go") //修改value
fmt.Printf("sValue的值修改后是%d\n",s)
type User struct { //定义结构体,指针修改结构体
Name string //这里注意要首字母大写,否则外面SetInt访问不到
Age int
}
u := User{ //给结构体赋值
Name: "liwenchao",
Age: 18,
}
uValue := reflect.ValueOf(&u) //读取struct的value
nValue := uValue.Elem()
fmt.Printf("uValue是可寻址的%t\n",nValue.CanAddr())
nValue.FieldByName("Age").SetInt(20) //把结构体内age的value由18改为20
fmt.Println(u.Age)
slice := make([]*User,3,5) //定义一个切片,修改切片的元素
slice[0] = &User { //定义一个切片的第一个元素
Name: "gaolili",
Age: 16,
}
sliceValue := reflect.ValueOf(slice) //读取slice切片的value
if sliceValue.Len() > 0 { //判断切片长度大于0
sliceValue.Index(0).Elem().FieldByName("Name").SetString("gll") //把切片的第一个元素的value,根据Name修改Name的value
}
fmt.Println(slice[0].Name)
reflect.ValueOf(&slice).Elem().SetCap(4) //修改切片的cap容量。注意这里定义切片的时候是5,cap只能比5小,不能打,否则报错。这算是go的保护机制
fmt.Println(cap(slice)) //打印改完后的切片容量
reflect.ValueOf(&slice).Elem().SetLen(4) //修改切片长度
fmt.Println(len(slice))
u1 := User{ //反射修改map
Name: "abc",
Age: 11,
}
userMap := make(map[int]*User,5) //定义一个map,key:value分别对应int类型和User的结构体
userMap[1] = &u1 //定义map的第一个key:value是 1和u1这个结构体
mapValue := reflect.ValueOf(userMap) //获取userMap的value
mapType := mapValue.Type()
fmt.Printf("mapValue数组的类型是%d\n",mapType) //获取mapValue的类型
fmt.Printf("mapValue数组的Key类型是%d\n",mapType.Key()) //获取mapValue的Key的类型
}