当前位置: 代码迷 >> 综合 >> 【Golang】反射技术:reflect包的使用方法总结
  详细解决方案

【Golang】反射技术:reflect包的使用方法总结

热度:95   发布时间:2023-12-05 21:14:35.0

       阅读Go源码时,发现很多库都离不开reflect包提供的强大的反射技术,但在基础学习时对这部分一闪而过,现在重新全面学习了一遍,主要使用方法都总结在下面的代码中,自以为应该是比较详细的,后期学习过程中如发现疏漏,再做补充。

       可以看到reflect包让我们获得了对于一个空接口中包含的值充分了解的能力,让我们的程序能够在运行时检查类型和变量,解析出一个未知类型背后的结构,对于以指针传递的结构,还可以得到修改字段和动态调用函数的权利,net/rpc就大量使用了reflect技术来进行服务的存储和动态调用。

      不再多说,代码如下:

package main
import ("fmt""reflect""time"
)
type OutType struct { // tagsfield1 bool   "This is field1's tag"Field2 string "This is field2's tag"Field3 int    "This is field3's tag"field4 *InType "This is field4's tag"
}
type InType struct {a int "this is tag of a"b int "this is tag of b"
}
func (o OutType)Function1(){fmt.Println("this is function1,it's unexported.")
}
func (o OutType)Function2(arg1 string,arg2 *int)(res1 int,res *string){fmt.Println("this is function2,it's exported.")return 0,nil
}
func (o *OutType)Function3(a,b int)(InType){fmt.Println("this is function2,it's exported.")return InType{a,b}
}
func main() {var obj interface{}=OutType{true,"hello",1,&InType{1,2}}var objPtr interface{}=&OutType{true,"hello",1,&InType{1,2}}///1、Type类型:包含了一个结构的类型信息(各数据成员和方法信息),不包含实际值,接口中包含了大量类型信息读取方法fmt.Println(reflect.TypeOf(obj))   //main.OutTypefmt.Println(reflect.TypeOf(objPtr)) //*main.OutTypeobjType:=reflect.TypeOf(obj)fmt.Println(objType.Size())   //40fmt.Println(objType.Kind())   //struct    kind()总显示底层类型fmt.Println(objType.Name())   //OutType//用指针初始化Type实例objPtrType:=reflect.TypeOf(objPtr)fmt.Println(objPtrType.Size())  //8fmt.Println(objPtrType.Kind())  //ptrfmt.Println(objPtrType.Name())  //空值///2、Value类型:包含了Type类型和对应的实际值,是reflect包中代表一个实例的类型//   ValueOf returns a new Value initialized to the concrete value stored in the interface//   源码注释:返回一个根据输入的接口中存储的值初始化的新Value类型,其包含的Type与输入接口相同//   可以理解为将输入的接口初始化为reflect包中的实例objPtrValue:=reflect.ValueOf(objPtr)objPtr.(*OutType).field1=false   //对指针指向属性的修改,会同时修改之前生成的Value对象fmt.Println(objPtr)       //&{false hello 1 0xc00000a0e0}fmt.Println(objPtrValue)   //&{false hello 1 0xc00000a0e0}objValue:=reflect.ValueOf(obj)    //用值对象初始化Value实例,只复制了一份值,相当于按值传递obj=OutType{false,"hello",1,&InType{1,2}}fmt.Println(obj)   //{false hello 1 0xc00000a140}fmt.Println(objValue)  //{true hello 1 0xc00000a0d0}//同样可以从Value实例中获得对应的Type信息typeFromobjValue:=objValue.Type()fmt.Println(typeFromobjValue.Name())  //OutType///3、对于使用包含指针型值的接口初始化的Value和Type,无法直接获得指向类型的信息//   可以通过一下方式,提取valuel类型指针指向的值,到一个新的value类型中,此时就获得了非指针类型//   在输入不确定是否为指针型的情况下,可以使用这种方法确保操作的是非指针型实例fmt.Println(objPtrType.Name())   //空elem1:=reflect.ValueOf(objPtr).Elem()elem2:=reflect.Indirect(reflect.ValueOf(objPtr))fmt.Println(elem1.Type().Name())   //OutTypefmt.Println(elem2.Type().Name())   //OutTypefmt.Println(reflect.ValueOf(objPtr).Elem().Type().Name())   //OutTypefmt.Println(reflect.Indirect(reflect.ValueOf(objPtr)).Type().Name())   //OutType///4、同样的,reflect包 支持将非指针类型转化为指针类型进行处理ptrValue:=reflect.PtrTo(objType)fmt.Println(ptrValue.Kind())   //ptrfmt.Println(ptrValue)          //*main.OutType///5、使用type类型:获得类型内各字段信息Field实例,其结构如下://type StructField struct {//	Name string   字段的名称//	PkgPath string  标志着一个非导出(首字母小写)字段的包路径,如果是导出量,该值为空(因此可以用于判断是否为导出量)//	Type      Type      字段类型,与外部的的实例类型没什么不同,因此可以做域中数据的进一步判断//	Tag       StructTag  字段后标签的字符串//	Offset    uintptr   // offset within struct, in bytes//	Index     []int     // index sequence for Type.FieldByIndex//	Anonymous bool     是否是一个嵌入的(匿名的)域//}for i:=1;i<=objType.NumField();i++{field:=objType.Field(i-1)fType:=field.TypefName:=field.NamefPkgPath:=field.PkgPathfTag:=field.Tagfmt.Printf("Field%v  type:%v  name:%v  PkgPath:%v  tag:%v \n",i,fType,fName,fPkgPath,fTag)//Field1  type:bool  name:field1  PkgPath:main  tag:This is field1's tag//Field2  type:string  name:Field2  PkgPath:  tag:This is field2's tag//Field3  type:int  name:Field3  PkgPath:  tag:This is field3's tag//Field4  type:*main.InType  name:field4  PkgPath:main  tag:This is field4's tag}//得到Field的Type字段,就可以查看类型内类型的具体信息intype:=objType.Field(3).Typefmt.Println(intype)   //*main.InTypeinObj:=intype.Elem()fmt.Println(inObj.Field(0).Name)  //a//可以通过函数FieldByName(name string) (StructField, bool),用字段名称得到字段类型field,_:=objType.FieldByName("field1");fmt.Println(field.Tag)   //This is field1's tag///6、使用Type类型:获得实例下的方法信息//type Method struct {//	Name    string  方法名//	PkgPath string   包路径名,同样可以被用来判断是否是被导出函数//	Type  Type  方法类型//	Func  Value 以接收者为第一参数的函数体//	Index int   方法下标//}fmt.Println(objType.NumMethod())   //2for i:=1;i<=objType.NumMethod();i++{mtd:=objType.Method(i-1)mName:=mtd.NamemType:=mtd.TypemPkgPath:=mtd.PkgPathfmt.Printf("Method%v  %v %v %v \n",i,mName,mType,mPkgPath)//Method1  Function1 func(main.OutType)//Method2  Function2 func(main.OutType, string, *int) (int, *string)}//与正常方法调用要求相同,以指针作为接收体的方法必需以指针实例调用//因此,只有指针化的结构才能反射到第三个函数fmt.Println(reflect.PtrTo(objType).Method(2).Name)    //Function3///7、对方法类型的处理//   方法对应的类型同样实现了Type接口,可以获得其中的字段信息mtdType:=objType.Method(1).Typefmt.Println(mtdType)   //func(main.OutType, string, *int) (int, *string)fmt.Println(mtdType.Kind()) //func//同样提供了按方法名获得方法类型的函数fun,_:=objType.MethodByName("Function1")fmt.Println(fun.Type)//func(main.OutType)//在方法中最关键的当然是参数和返回值返回值信息,使用Out和In函数,同样可以得到对应的Type类型//值得注意的是,方法的接收者类型被作为第一个参数进行处理for i:=0;i<mtdType.NumIn();i++{argType:=mtdType.In(i)if argType.Kind()==reflect.Ptr{   //对于有可能是指针的量,可以与relect.Ptr进行对比做出判断fmt.Println(argType.Elem().Name())}else{fmt.Println(argType.Name())}}for i:=0;i<mtdType.NumOut();i++{resType:=mtdType.Out(i)if resType.Kind()==reflect.Ptr{fmt.Println(resType.Elem().Name())}else{fmt.Println(resType.Name())}}///8、除解析一个接口的结构字段和方法外,还可以对注册在结构上的方法进行调用//   调用过程中需要注意接收者是否为指针型,被调用函数应当是被导出类型//   当然,这个调用过程只能在包含了结构实例值的Value类型上使用,Type类型无法嗲用//    func (v Value) Call(in []Value) []Value//   参数和返回值都是reflect包中Value型的切片,需要经过转换objValue.Method(0).Call(nil)   //函数打印:this is function1,it's unexported.res:=objPtrValue.Method(2).Call([]reflect.Value{reflect.ValueOf(1),reflect.ValueOf(2)})//this is function2,it's exported.fmt.Println(res[0])  //{1 2}//对于Type类型包含的方法,因为没有实际值作为接收者,需要吧传入的第一个参数作为接收者function:=objType.Method(0).Funcfunction.Call([]reflect.Value{reflect.ValueOf(obj)})//this is function1,it's unexported.///9、reflect包提供了方法对Value实例中的字段值做出修改//   To modify a reflection object, the value must be settable.//   值得注意的是,根据官方描述,为了改变一个反射对象,其值必需是可修改的//   这里的可修改值得注意,我们知道,通过输入的接口初始化的Value实例//   返回一个根据输入的接口中存储的值初始化的新Value类型(跟按值传递参数一致)//   因此,对Value中的值做出修改并没有改变外部接口值,因此并不支持这样做//   objValue.Field(1).SetString("hello,world!")//   panic: reflect: reflect.flag.mustBeAssignable using unaddressable value//   可行的用法是使用Elem()函数将用指针量初始化的Value实例中实际存储的值(相当于引用传递,这里的Elem()相当于一个解引用)fmt.Println(objPtrValue.Elem().Field(1))   //helloobjPtrValue.Elem().Field(1).SetString("hello,world!")fmt.Println(objPtrValue.Elem().Field(1))    //hello,world!//可以使用CanSet()函数判断是否能够修改值fmt.Println(objValue.Field(1).CanSet())   //falsefmt.Println(objPtrValue.Elem().Field(1).CanSet())  //true/////11、对切片进行的一些操作slice:=[]int{}slice=append(slice,1,2,3,4,5,6,7,8,9)sliceValue:=reflect.ValueOf(&slice).Elem()fmt.Println(sliceValue.Len())       //9fmt.Println(sliceValue.Cap())       //10sliceValue.SetLen(10)fmt.Println(sliceValue.Len())       //10fmt.Println(sliceValue.Index(3)) //4/////11、补充//对于kind()返回的底层类型的判断举例fmt.Println(objPtrType.Kind()==reflect.Ptr)   //truefmt.Println(objType.Kind()==reflect.Struct)   //truevar m=make(map[int]int)fmt.Println(reflect.TypeOf(m).Kind()==reflect.Map) //true//reflect包提供func New(typ Type) Value函数,根据输入类型默认初始化一个Value// 该Value量包含的是一个指针型实例,也就是New函数和包外的New一样,申请好了内存空间,其中值可修改newType:=reflect.New(objType)fmt.Println(newType)   //&{false  0 <nil>}fmt.Println(newType.Elem().Field(0).CanSet())  //true//可以使用Interface()函数将Value值转化回对应的interfacefmt.Println(objValue.Interface().(OutType).Field2)//hello//对于管道值的操作,reflect提供了send函数ch:=make(chan int)go reflect.ValueOf(ch).Send(reflect.ValueOf(1))fmt.Println(<-ch)  //1time.Sleep(1e9)
}

 

  相关解决方案