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的类型
}
posted @ 2022-03-31 14:35  liwenchao1995  阅读(67)  评论(0编辑  收藏  举报