go标准库的学习-reflect
参考:
https://studygolang.com/pkgdoc
http://c.biancheng.net/golang/concurrent/
导入方式:
import "reflect"
reflect包实现了运行时反射,允许程序操作任意类型的对象。主要是实现了泛型,比如在一个函数中能根据传入的参数来确定参数的类型,而不是一开始就指定参数类型,这样一个函数就可以支持所有类型:
func Minimum(first interface{}, rest ...interface{}) interface{} { //... }
这样就使得静态的go有了很多动态的特性,reflect是配合interface{}来使用的
典型用法是用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值。调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。Zero接受一个Type类型参数并返回一个代表该类型零值的Value类型值。
反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
1.类型(Type)与种类(Kind)
在使用反射时,需要首先理解类型(Type)和种类(Kind)的区别。编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。
1)种类(Kind)指的是对象归属的品种,在 reflect 包中有如下定义:
type Kind uint
Kind代表Type类型值表示的具体分类。零值表示非法分类。
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 (Kind) String
func (k Kind) String() string
定义k的字符串格式
2)类型(Type)
Go 程序中的类型(Type)指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型。
type Type
type Type interface { // Kind返回该接口的具体分类 Kind() Kind // Name返回该类型在自身包内的类型名,如果是未命名类型会返回"" Name() string // PkgPath返回类型的包路径,即明确指定包的import路径,如"encoding/base64" // 如果类型为内建类型(string, error)或未命名类型(*T, struct{}, []int),会返回"" PkgPath() string // 返回类型的字符串表示。该字符串可能会使用短包名(如用base64代替"encoding/base64") // 也不保证每个类型的字符串表示不同。如果要比较两个类型是否相等,请直接用Type类型比较。 String() string // 返回要保存一个该类型的值需要多少字节;类似unsafe.Sizeof Size() uintptr // 返回当从内存中申请一个该类型值时,会对齐的字节数 Align() int // 返回当该类型作为结构体的字段时,会对齐的字节数 FieldAlign() int // 如果该类型实现了u代表的接口,会返回真 Implements(u Type) bool // 如果该类型的值可以直接赋值给u代表的类型,返回真 AssignableTo(u Type) bool // 如该类型的值可以转换为u代表的类型,返回真 ConvertibleTo(u Type) bool // 返回该类型的字位数。如果该类型的Kind不是Int、Uint、Float或Complex,会panic Bits() int // 返回array类型的长度,如非数组类型将panic Len() int // 返回该类型的元素类型,如果该类型的Kind不是Array、Chan、Map、Ptr或Slice,会panic Elem() Type // 返回map类型的键的类型。如非映射类型将panic Key() Type // 返回一个channel类型的方向,如非通道类型将会panic ChanDir() ChanDir
// 返回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 // 返回func类型的第i个参数的类型,如非函数或者i不在[0, NumIn())内将会panic In(i int) Type // 返回func类型的返回值个数,如果不是函数,将会panic NumOut() int // 返回func类型的第i个返回值的类型,如非函数或者i不在[0, NumOut())内将会panic Out(i int) 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) // 内含隐藏或非导出方法 }
Type类型用来表示一个go类型。
不是所有go类型的Type值都能使用所有方法。请参见每个方法的文档获取使用限制。
在调用有分类限定的方法时,应先使用Kind()方法获知类型的分类。
调用该分类不支持的方法会导致运行时的panic。
func TypeOf
func TypeOf(i interface{}) Type
TypeOf返回接口中保存的值的类型,TypeOf(nil)会返回nil。然后就能够使用返回的Type值调用上面的方法获得对象的内容
1》当反射对象的类型是原生数据类型时
举例:
package main import( "fmt" "reflect" ) func main() { var a int typeA := reflect.TypeOf(a) fmt.Println(typeA) //int fmt.Println(typeA.Name()) //int,返回表示类型名称的字符串 fmt.Println(typeA.Kind()) //int,返回 reflect.Kind 类型的常量 }
2》如果反射对象的类型是指针时
如果从指针中获取反射对象时,这是不能直接使用Name()、Kind(),这样得到的只是指针的信息(即name为空'',kind为'ptr')。如果想要得到的是该指针指向的变量的类型名称河种类,就要使用Elem()
package main import( "fmt" "reflect" ) func main() { //创建一个结构体Student type Student struct{ name string school string } //创建一个Student实例指针 stuPtr := &Student{"xiaoming", "peking university"} typeStuPtr := reflect.TypeOf(stuPtr) fmt.Println(typeStuPtr.Name()) //为空 fmt.Println(typeStuPtr.Kind()) //ptr //获取该指针的元素 typeStu := typeStuPtr.Elem() fmt.Println(typeStu.Name()) //Student fmt.Println(typeStu.Kind()) //struct }
如果使用的不是指针那就能够正常地使用Name() 、Kind()
package main import( "fmt" "reflect" ) func main() { //创建一个结构体Student type Student struct{ name string school string } stu := Student{"xiaoming", "peking university"} typeStu := reflect.TypeOf(stu) fmt.Println(typeStu.Name()) //Student fmt.Println(typeStu.Kind()) //struct }
3)当反射对象的类型是结构体时
任意值通过 reflect.TypeOf() 获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的 NumField() 和 Field() 方法获得结构体成员的详细信息。与成员获取相关的 reflect.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)
结构体字段类型
type StructField
type StructField struct { // Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。 // 参见http://golang.org/ref/spec#Uniqueness_of_identifiers Name string PkgPath string //字段在结构体中的路径 Type Type // 字段本身的反射类型对象,类型为 reflect.Type,可以进一步获取字段的类型信息 Tag StructTag // 字段的标签 Offset uintptr // 字段在结构体中的字节偏移量 Index []int // 用于Type.FieldByIndex时的索引切片 Anonymous bool // 是否匿名字段 }
StructField类型描述结构体中的一个字段的信息。
// 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic Field(i int) StructField
Field()等方法的返回值就是StructField,所以能够使用Field(0).Tag来调用标签
结构体字段中的标签
type StructTag
type StructTag string
StructTag是结构体字段的标签。
一般来说,标签字符串是(可选的)空格分隔的一连串`key1:"value1" key2:"value2"`对,该格式的要求比较严格,不能在key和value中随意添加空格,否则会因为格式错误导致一系列问题。
⚠️每个键都是不包含控制字符、空格、双引号、冒号的非空字符串。每个值都应被双引号括起来,使用go字符串字面语法。
func (StructTag) Get
func (tag StructTag) Get(key string) string
Get方法返回标签字符串`key:"value"`对中键key对应的值。如果标签中没有该键,会返回""。如果标签不符合标准格式,Get的返回值是不确定的。
举例说明:
package main
import(
"fmt"
"reflect"
)
func main() {
//创建一个结构体Student
type Student struct{
Name string
School string `level:"national" id:"1"` //注明有两个标签level和id,用于给字段添加自定义信息,方便其他模块根据信息进行不同功能的处理
//注意,注明标签的时候不要随便添加空格,否则会因为格式的问题在调用Get()函数时得不到结果
}
stu := Student{"xiaoming", "peking university"}
//获取结构体实例的反射类型对象
typeStu := reflect.TypeOf(stu)
fmt.Println(typeStu.NumField()) //2
//遍历获得结构体的所有字段
for i := 0; i < typeStu.NumField(); i++{
//获取对应的字段类型
fieldType := typeStu.Field(i)
fmt.Printf("fieldType: %v\n", fieldType)
//循环两次返回:
//fieldType: {Name string 0 [0] false}
//fieldType: {School string level:"national" id:"1" 16 [1] false}
//打印获取的字段名以及其注明的标签信息
fmt.Printf("name : %v tag : %v\n", fieldType.Name, fieldType.Tag)//fieldType.Tag返回StructTag类型的值,然后可以调用Get()函数得到具体的tag值
//循环两次返回:
//name : Name tag :
//name : School tag : level:"national" id:"1"
}
//通过字段名,找到字段的类型信息
if studentSchool, ok := typeStu.FieldByName("School"); ok {
// fmt.Printf("studentSchool: %v\n", studentSchool)//studentSchool: {School string level:"national" id:"1" 16 [1] false}
//使用Get()获取标签的value
fmt.Println(studentSchool.Tag.Get("level"), studentSchool.Tag.Get("id"))//national 1
}
}
4)Type中的其他函数
func PtrTo
func PtrTo(t Type) Type
PtrTo返回类型t的指针的类型。例如,如果 t 表示类型 Foo ,则 PtrT(t) 表示 * Foo
package main import( "fmt" "reflect" ) func main() { //创建一个结构体Student type Student struct{ name string school string } //创建一个Student实例指针 stuPtr1 := &Student{"xiaoming", "peking university"} typeStuPtr1 := reflect.TypeOf(stuPtr1) fmt.Println(typeStuPtr1) //*main.Student fmt.Println(typeStuPtr1.Name()) //为空 fmt.Println(typeStuPtr1.Kind()) //ptr //获取该指针的元素 typeStu1 := typeStuPtr1.Elem() fmt.Println(typeStu1.Name()) //Student fmt.Println(typeStu1.Kind()) //struct //上面直接传入指针给TypeOf和下面使用PtrTo将其转成指针类型的效果是一样的 stu2 := Student{"zhangwei", "qinghua university"} typeStu2 := reflect.TypeOf(stu2) fmt.Println(typeStu2) //main.Student typeStuPtr2 := reflect.PtrTo(typeStu2) fmt.Println(typeStuPtr2) //*main.Student fmt.Println(typeStuPtr2.Name()) //为空 fmt.Println(typeStuPtr2.Kind()) //ptr //获取该指针的元素 typeStu22 := typeStuPtr2.Elem() fmt.Println(typeStu22.Name()) //Student fmt.Println(typeStu22.Kind()) //struct }
func SliceOf
func SliceOf(t Type) Type
SliceOf返回类型t的切片的类型。例如,如果 t 表示 int , SliceOf(t) 表示 [] int
package main import( "fmt" "reflect" ) func main() { //创建一个结构体Student type Student struct{ name string school string } stu := Student{"zhangwei", "qinghua university"} typeStu := reflect.TypeOf(stu) fmt.Println(typeStu) //main.Student typeStuSlice := reflect.SliceOf(typeStu) fmt.Println(typeStuSlice) //[]main.Student,让其转成一个切片类型 fmt.Println(typeStuSlice.Name()) //为空 fmt.Println(typeStuSlice.Kind()) //slice //获取该指针的元素,当然切片中的元素的类型还是不变的 typeStuSliceElem := typeStuSlice.Elem() fmt.Println(typeStuSliceElem.Name()) //Student fmt.Println(typeStuSliceElem.Kind()) //struct }
func MapOf
func MapOf(key, elem Type) Type
MapOf返回一个键类型为key,值类型为elem的映射类型。如果key代表的类型不是合法的映射键类型(即它未实现go的==操作符),本函数会panic。
例如,如果 key 表示 int 并且 elem 表示 string,则 MapOf(k, e) 表示 map[int] string
package main import( "fmt" "reflect" ) func main() { //创建一个结构体Student type Student struct{ name string school string } stu := Student{"zhangwei", "qinghua university"} key := 1 elemTypeStu := reflect.TypeOf(stu) fmt.Println(elemTypeStu) //main.Student keyTypeInt := reflect.TypeOf(key) fmt.Println(keyTypeInt) //int typeMap := reflect.MapOf(keyTypeInt, elemTypeStu) fmt.Println(typeMap) //map[int]main.Student,让其转成一个映射类型 fmt.Println(typeMap.Name()) //为空 fmt.Println(typeMap.Kind()) //map //获取该映射的value的元素,当然value中的元素的类型还是不变的为Student typeMapElem := typeMap.Elem() fmt.Println(typeMapElem.Name()) //Student fmt.Println(typeMapElem.Kind()) //struct }
func ChanOf
func ChanOf(dir ChanDir, t Type) Type
ChanOf返回元素类型为t、方向为dir的通道类型。运行时GC强制将通道的元素类型的大小限定为64kb。如果t的尺寸大于或等于该限制,本函数将会panic。
例如,如果 t 表示 int ,则 ChanOf(RecvDir,t) 表示 <-chan int
2.通过反射获取值Value
反射不仅能和上面一样获取值的类型(Type)信息,还可以动态地获取或设置变量的值
type Value
type Value struct { // 存储使用Value表示的值的类型 typ *rtype // 指针值数据,如果flagIndir设置,则指向数据 // 当flagIndir设置或typ.pointers()为true时有效 ptr unsafe.Pointer // 存储值的元数据 flag }
Value为go值提供了反射接口。
不是所有go类型值的Value表示都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知该值的分类。调用该分类不支持的方法会导致运行时的panic。
Value类型的零值表示不持有某个值。零值的IsValid方法返回false,其Kind方法返回Invalid,而String方法返回"<invalid Value>",所有其它方法都会panic。绝大多数函数和方法都永远不返回Value零值。如果某个函数/方法返回了非法的Value,它的文档必须显式的说明具体情况。
如果某个go类型值可以安全的用于多线程并发操作,它的Value表示也可以安全的用于并发。
主要使用下面的方法获取反射之对象(reflect.Value):
func ValueOf
func ValueOf(i interface{}) Value
ValueOf返回一个初始化为i接口保管的具体值的Value,ValueOf(nil)返回Value零值。
一些常用方法:
func (Value) Kind
func (v Value) Kind() Kind
Kind返回v持有的值的分类,如果v是Value零值,返回值为Invalid
func (Value) Type
func (v Value) Type() Type
返回v持有的值的类型的Type表示。
func (Value) Elem
func (v Value) Elem() Value
Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。
举例:
package main import( "fmt" "reflect" ) func main() { a := 1 //声明并赋值得到一个int类型的a //获取其的反射值对象 valueOfA := reflect.ValueOf(a) fmt.Println(valueOfA) //1 fmt.Println(valueOfA.Kind()) //int //获取该值的类型,注意elem.(type)这种方法只能用在switch中 fmt.Println(reflect.TypeOf(valueOfA)) //reflect.Value //使用Value的Type()方法得到的是该对象的原始类型 fmt.Println(valueOfA.Type()) //int fmt.Println(valueOfA.Elem) //0x1091910 }
1)从反射值对象(reflect.Value)中获取值的方法:
func (Value) Interface
func (v Value) Interface() (i interface{})
本方法返回v当前持有的值(表示为/保管在interface{}类型),等价于:
var i interface{} = (v's underlying value)
如果v是通过访问非导出结构体字段获取的,会导致panic。
func (Value) Bool
func (v Value) Bool() bool
返回v持有的布尔值,如果v的Kind不是Bool会panic
func (Value) Int
func (v Value) Int() int64
返回v持有的有符号整数(表示为int64),如果v的Kind不是Int、Int8、Int16、Int32、Int64会panic
func (Value) Uint
func (v Value) Uint() uint64
返回v持有的无符号整数(表示为uint64),如v的Kind不是Uint、Uintptr、Uint8、Uint16、Uint32、Uint64会panic
func (Value) Float
func (v Value) Float() float64
返回v持有的浮点数(表示为float64),如果v的Kind不是Float32、Float64会panic
func (Value) Complex
func (v Value) Complex() complex128
返回v持有的复数(表示为complex64),如果v的Kind不是Complex64、Complex128会panic
func (Value) Pointer
func (v Value) Pointer() uintptr
将v持有的值作为一个指针返回。本方法返回值不是unsafe.Pointer类型,以避免程序员不显式导入unsafe包却得到unsafe.Pointer类型表示的指针。如果v的Kind不是Chan、Func、Map、Ptr、Slice或UnsafePointer会panic。
如果v的Kind是Func,返回值是底层代码的指针,但并不足以用于区分不同的函数;只能保证当且仅当v持有函数类型零值nil时,返回值为0。
如果v的Kind是Slice,返回值是指向切片第一个元素的指针。如果持有的切片为nil,返回值为0;如果持有的切片没有元素但不是nil,返回值不会是0。
func (Value) Bytes
func (v Value) Bytes() []byte
返回v持有的[]byte类型值。如果v持有的值的类型不是[]byte会panic。
func (Value) String
func (v Value) String() string
返回v持有的值的字符串表示。因为go的String方法的惯例,Value的String方法比较特别。和其他获取v持有值的方法不同:v的Kind是String时,返回该字符串;v的Kind不是String时也不会panic而是返回格式为"<T value>"的字符串,其中T是v持有值的类型。
举例:
package main import( "fmt" "reflect" ) func main() { a := 1 //声明并赋值得到一个int类型的a //获取其的反射值对象 valueOfA := reflect.ValueOf(a) fmt.Println(valueOfA) //1 //获取该值的类型,注意elem.(type)这种方法只能用在switch中 fmt.Println(reflect.TypeOf(valueOfA)) //reflect.Value //调用interface()方法获取interface{}类型的值,然后使用类型断言进行转换成int类型 changedA1 := valueOfA.Interface().(int) fmt.Println(changedA1) //1 fmt.Println(reflect.TypeOf(changedA1)) //int //还有另一种类似的方法,就是调用Int()方法将其先转换成int64类型,然后再转成int类型 changedA2 := int(valueOfA.Int()) fmt.Println(changedA2) //1 fmt.Println(reflect.TypeOf(changedA2))//int }
当然,其他的方法都是类似的,这几个函数的作用就是将Value类型的值又转回其原生数据类型
2)如果反射值对象的类型是结构体,其可以使用下面的几种方法:
func (Value) NumField
func (v Value) NumField() int
返回v持有的结构体类型值的字段数,如果v的Kind不是Struct会panic
func (Value) Field
func (v Value) Field(i int) Value
返回结构体的第i个字段(的Value封装)。如果v的Kind不是Struct或i出界会panic
上面两个方法可以结合用来循环获取字段内容
func (Value) FieldByIndex
func (v Value) FieldByIndex(index []int) Value
返回索引序列指定的嵌套字段的Value表示,等价于用索引中的值链式调用本方法,如v的Kind非Struct将会panic
func (Value) FieldByName
func (v Value) FieldByName(name string) Value
返回该类型名为name的字段(的Value封装)(会查找匿名字段及其子字段),如果v的Kind不是Struct会panic;如果未找到会返回Value零值。
func (Value) FieldByNameFunc
func (v Value) FieldByNameFunc(match func(string) bool) Value
返回该类型第一个字段名满足match的字段(的Value封装)(会查找匿名字段及其子字段),如果v的Kind不是Struct会panic;如果未找到会返回Value零值。
举例:
package main import( "fmt" "reflect" ) func main() { //创建一个结构体Student type Student struct{ name string school string } stu := Student{"xiaoming", "peking university"} valueStu := reflect.ValueOf(stu) //使用NumField()得到结构体中字段的数量,然后迭代得到字段的值Field(i)和类型Field(i).Type() for i := 0; i < valueStu.NumField(); i++{ fmt.Printf("fieldValue: %v, FieldType: %v\n", valueStu.Field(i), valueStu.Field(i).Type()) //迭代返回: // fieldValue: xiaoming, FieldType: string // fieldValue: peking university, FieldType: string } fmt.Println() //根据名字查找字段 fmt.Printf("fieldValue: %v, FieldType: %v\n", valueStu.FieldByName("name"), valueStu.FieldByName("name").Type()) //fieldValue: xiaoming, FieldType: string //根据索引值查找字段,[]int{1}的意思就是读取索引为1的字段,如果该字段也是个结构体,如果想获得该字段的索引为2的值,应写成[]int{1,2} fmt.Printf("fieldValue: %v, FieldType: %v\n", valueStu.FieldByIndex([]int{1}), valueStu.FieldByIndex([]int{1}).Type()) //fieldValue: peking university, FieldType: string }
3)判断反射值的空和有效性
func (Value) IsNil
func (v Value) IsNil() bool
IsNil报告v持有的值是否为nil,常用于判断指针是否为空
v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic。
注意IsNil并不总是等价于go语言中值与nil的常规比较。例如:如果v是通过使用某个值为nil的接口调用ValueOf函数创建的,v.IsNil()返回真,但是如果v是Value零值,会panic。
func (Value) IsValid
func (v Value) IsValid() bool
IsValid返回v是否持有一个值,常用于判断返回值是否有效
如果v是Value零值会返回false,此时v除了IsValid、String、Kind之外的方法都会导致panic。绝大多数函数和方法都永远不返回Value零值。
如果某个函数/方法返回了非法的Value,它的文档必须显式的说明具体情况。
举例:
package main import( "fmt" "reflect" ) func main() { //nil值 // fmt.Printf(" nil isNil :%v\n", reflect.ValueOf(nil).IsNil())//panic: reflect: call of reflect.Value.IsNil on zero Value fmt.Printf(" nil isValid :%v\n", reflect.ValueOf(nil).IsValid()) //空指针 var a *int fmt.Printf(" a *int isNil :%v\n", reflect.ValueOf(a).IsNil()) fmt.Printf(" a *int isValid :%v\n", reflect.ValueOf(a).IsValid()) //空映射 m := map[int]int{} fmt.Printf(" m map[int]int{} isNil :%v\n", reflect.ValueOf(m).IsNil()) fmt.Printf(" m map[int]int{} isValid :%v\n", reflect.ValueOf(m).IsValid()) //获取其中不存在的键 fmt.Printf(" m map[3]'s value' isValid :%v\n", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid()) //空结构体 s := struct{}{} // fmt.Printf(" s struct{}{} isNil :%v\n", reflect.ValueOf(s).IsNil())//panic: reflect: call of reflect.Value.IsNil on struct Value fmt.Printf(" s struct{}{} isValid :%v\n", reflect.ValueOf(s).IsValid()) //获取一个不存在的字段 fmt.Printf(" s.name isValid :%v\n", reflect.ValueOf(s).FieldByName("name").IsValid()) //获取一个不存在的方法 fmt.Printf(" s method isValid :%v\n", reflect.ValueOf(s).MethodByName("Method").IsValid()) }
返回:
userdeMBP:go-learning user$ go run test.go nil isValid :false a *int isNil :true a *int isValid :true m map[int]int{} isNil :false m map[int]int{} isValid :true m map[3]'s value' isValid :false s struct{}{} isValid :true s.name isValid :false s method isValid :false
4)通过反射修改变量的值
1》获取反射值元素即判断其是否能够进行修改的方法:
func (Value) Elem
func (v Value) Elem() Value
Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装,类似于*操作,此时的Value表示的是Value的元素且可以寻址。
如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。
func (Value) Addr
func (v Value) Addr() Value
函数返回一个持有指向v持有者的指针的Value封装,类似于&操作。
如果v.CanAddr()返回假,调用本方法会panic。Addr一般用于获取结构体字段的指针或者切片的元素(的Value封装)以便调用需要指针类型接收者的方法。
func (Value) CanAddr
func (v Value) CanAddr() bool
返回是否可以获取v持有值的指针。可以获取指针的值被称为可寻址的。
如果一个值是切片或可寻址数组的元素、可寻址结构体的字段、或从指针解引用得到的,该值即为可寻址的。
func (Value) CanSet
func (v Value) CanSet() bool
如果v持有的值可以被修改,CanSet就会返回真。
⚠️一个是否能够被修改的值必须满足下面的两个条件
只有一个Value持有值 1>可以被寻址 2>同时又不是来自非导出字段时,它才可以被修改。
如果CanSet返回假,调用Set或任何限定类型的设置函数(如SetBool、SetInt64)都会panic。
1>可被寻址
package main import( "fmt" "reflect" ) func main() { a := 1 ValueA := reflect.ValueOf(a) fmt.Println(ValueA.CanSet()) //false fmt.Println(ValueA.CanAddr()) //false ValueAPtr := reflect.ValueOf(&a) ValueA2 := ValueAPtr.Elem() //此时的ValueA2表示的是a的元素且可以寻址 fmt.Println(ValueA2.CanSet()) //true fmt.Println(ValueA2.CanAddr()) //true ValueA2.SetInt(7) //修改值为7 fmt.Println(ValueA2.Int()) //7 }
2>被导出
比如在结构体中,如果你的字段命名是第一个字符为小写则说明该字段是不被导出的
package main import( "fmt" "reflect" ) func main() { //创建一个结构体Student type Student struct{ name string //小写,不被导出 School string //大写,被导出 } stu := Student{"xiaoming", "peking university"} valueStuPtr := reflect.ValueOf(&stu) valueStu := valueStuPtr.Elem() //可寻址 fmt.Println(valueStu.CanSet()) //true fmt.Println(valueStu.CanAddr()) //true stuName := valueStu.FieldByName("name") fmt.Println(stuName.CanAddr()) //true,可寻址 fmt.Println(stuName.CanSet()) //false,但是因为不被导出,所以不可以修改 stuSchool:= valueStu.FieldByName("School") fmt.Println(stuSchool.CanSet()) //true fmt.Println(stuSchool.CanAddr()) //true stuSchool.SetString("qinghua university") fmt.Println(stuSchool.String())//qinghua university }
2)值修改时调用的方法:
func (Value) SetBool
func (v Value) SetBool(x bool)
设置v的持有值。如果v的Kind不是Bool或者v.CanSet()返回假,会panic。
func (Value) SetInt
func (v Value) SetInt(x int64)
设置v的持有值。如果v的Kind不是Int、Int8、Int16、Int32、Int64之一或者v.CanSet()返回假,会panic。
func (Value) SetUint
func (v Value) SetUint(x uint64)
设置v的持有值。如果v的Kind不是Uint、Uintptr、Uint8、Uint16、Uint32、Uint64或者v.CanSet()返回假,会panic。
func (Value) SetFloat
func (v Value) SetFloat(x float64)
设置v的持有值。如果v的Kind不是Float32、Float64或者v.CanSet()返回假,会panic。
func (Value) SetComplex
func (v Value) SetComplex(x complex128)
设置v的持有值。如果v的Kind不是Complex64、Complex128或者v.CanSet()返回假,会panic。
func (Value) SetBytes
func (v Value) SetBytes(x []byte)
设置v的持有值。如果v持有值不是[]byte类型或者v.CanSet()返回假,会panic。
func (Value) SetString
func (v Value) SetString(x string)
设置v的持有值。如果v的Kind不是String或者v.CanSet()返回假,会panic。
func (Value) SetPointer
func (v Value) SetPointer(x unsafe.Pointer)
设置v的持有值。如果v的Kind不是UnsafePointer或者v.CanSet()返回假,会panic。
func (Value) SetCap
func (v Value) SetCap(n int)
设定v持有值的容量。如果v的Kind不是Slice或者n出界(小于长度或超出容量),将导致panic
func (Value) SetLen
func (v Value) SetLen(n int)
设定v持有值的长度。如果v的Kind不是Slice或者n出界(小于零或超出容量),将导致panic
func (Value) SetMapIndex
func (v Value) SetMapIndex(key, val Value)
用来给v的映射类型持有值添加/修改键值对,如果val是Value零值,则是删除键值对。如果v的Kind不是Map,或者v的持有值是nil,将会panic。key的持有值必须可以直接赋值给v持有值类型的键类型。val的持有值必须可以直接赋值给v持有值类型的值类型。
func (Value) Set
func (v Value) Set(x Value)
将v的持有值修改为x的持有值。如果v.CanSet()返回假,会panic。x的持有值必须能直接赋给v持有值的类型。
3)通过反射调用函数
如果反射值对象(reflect.Value)中值的类型为函数时,可以通过 reflect.Value 调用该函数。
使用反射调用函数时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过 []reflect.Value 返回。
使用的方法:
func (Value) Call
func (v Value) Call(in []Value) []Value
Call方法使用输入的参数in调用v持有的函数。例如,如果len(in) == 3,v.Call(in)代表调用v(in[0], in[1], in[2])(其中Value值表示其持有值)。如果v的Kind不是Func会panic。它返回函数所有输出结果的Value封装的切片。和go代码一样,每一个输入实参的持有值都必须可以直接赋值给函数对应输入参数的类型。如果v持有值是可变参数函数,Call方法会自行创建一个代表可变参数的切片,将对应可变参数的值都拷贝到里面。
func (Value) CallSlice
func (v Value) CallSlice(in []Value) []Value
CallSlice调用v持有的可变参数函数,会将切片类型的in[len(in)-1](的成员)分配给v的最后的可变参数。例如,如果len(in) == 3,v.Call(in)代表调用v(in[0], in[1], in[2])(其中Value值表示其持有值,可变参数函数的可变参数位置提供一个切片并跟三个点号代表"解切片")。如果v的Kind不是Func或者v的持有值不是可变参数函数,会panic。它返回函数所有输出结果的Value封装的切片。和go代码一样,每一个输入实参的持有值都必须可以直接赋值给函数对应输入参数的类型。
举例:
package main import( "fmt" "reflect" ) func add(a, b int) int { return a + b } func main() { // 将函数add包装为反射值对象 funcValue := reflect.ValueOf(add) // 构造函数add的参数, 传入两个整型值 paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)} // 反射调用函数Call() retList := funcValue.Call(paramList) // 获取第一个返回值, 取整数值 fmt.Println(retList[0].Int()) //返回 30 }
⚠️
反射调用函数的过程需要构造大量的 reflect.Value 和中间变量,对函数参数值进行逐一检查,还需要将调用参数复制到调用函数的参数内存中。调用完毕后,还需要将返回值转换为 reflect.Value,用户还需要从中取出调用值。因此,反射调用函数的性能问题尤为突出,不建议大量使用反射函数调用。
其他相关函数有:
func (Value) NumMethod
func (v Value) NumMethod() int
返回v持有值的方法集的方法数目。
func (Value) Method
func (v Value) Method(i int) Value
返回v持有值类型的第i个方法的已绑定(到v的持有值的)状态的函数形式的Value封装。返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。如果i出界,或者v的持有值是接口类型的零值(nil),会panic。
func (Value) MethodByName
func (v Value) MethodByName(name string) Value
返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装。返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。如果未找到该方法,会返回一个Value零值。
举例:
package main import( "fmt" "reflect" ) type S struct{} func (s *S) Add(a, b int) int { return a + b } func main() { s := &S{} funcValue := reflect.ValueOf(s) // 构造函数add的参数, 传入两个整型值 paramList1 := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)} paramList2 := []reflect.Value{reflect.ValueOf(20), reflect.ValueOf(40)} fmt.Println(funcValue.NumMethod()) //1 //获得方法并反射调用函数Call() retList1 := funcValue.Method(0).Call(paramList1) fmt.Println(retList1[0].Int()) //30 retList2 := funcValue.MethodByName("Add").Call(paramList2) fmt.Println(retList2[0].Int()) //60 }
3.通过Type创建Value实例
1)new方法:
func New
func New(typ Type) Value
New返回一个Value类型值,该值持有一个指向类型为typ的新申请的零值的指针,返回值的Type为PtrTo(typ)。
func NewAt
func NewAt(typ Type, p unsafe.Pointer) Value
NewAt返回一个Value类型值,该值持有一个指向类型为typ、地址为p的值的指针。
举例:
package main import( "fmt" "reflect" ) func main() { a := 1 typeA := reflect.TypeOf(a) newA := reflect.New(typeA) //类似于New(int),返回的是*int类型 fmt.Println(newA.Kind()) //ptr fmt.Println(newA.Type()) //*int //获取该指针元素 elemNewA := newA.Elem() fmt.Println(elemNewA.Kind()) //int fmt.Println(elemNewA.Type()) //int }
2)make方法—只支持切片,函数,映射和通道:
func MakeSlice
func MakeSlice(typ Type, len, cap int) Value
MakeSlice创建一个新申请的元素类型为typ,长度len容量cap的切片类型的Value值。
func MakeMap
func MakeMap(typ Type) Value
MakeMap创建一个特定映射类型的Value值。
func MakeChan
func MakeChan(typ Type, buffer int) Value
MakeChan创建一个元素类型为typ、有buffer个缓存的通道类型的Value值。
func MakeFunc
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
MakeFunc返回一个具有给定类型、包装函数fn的函数的Value封装。当被调用时,该函数会
- 将提供给它的参数转化为Value切片 - 执行results := fn(args) - 将results中每一个result依次排列作为返回值
函数fn的实现可以假设参数Value切片匹配typ类型指定的参数数目和类型。如果typ表示一个可变参数函数类型,参数切片中最后一个Value本身必须是一个包含所有可变参数的切片。fn返回的结果Value切片也必须匹配typ类型指定的结果数目和类型。
Value.Call方法允许程序员使用Value调用一个有类型约束的函数;反过来,MakeFunc方法允许程序员使用Value实现一个有类型约束的函数。
下例是一个用MakeFunc创建一个生成不同参数类型的swap函数的代码及其说明。
package main import( "fmt" "reflect" ) func main() { swap := func(in []reflect.Value) []reflect.Value { return []reflect.Value{in[1], in[0]} } makeSwap := func(fptr interface{}) { //得到传入指针的元素 fn := reflect.ValueOf(fptr).Elem() //将传给fptr指针表示的函数的参数args转化成[]reflect.Value类型作为swap函数的in参数传入 //然后运行swap([]reflect.Value{args}),然后得到[]Value类型的结果results //然后将results中的值依次排列作为值v传出 v := reflect.MakeFunc(fn.Type(), swap)//等待参数传入 fn.Set(v) } var intSwap func(int, int) (int, int) makeSwap(&intSwap) //这个就是将参数0,1传入,运行swap fmt.Println(intSwap(0, 1)) //返回:1 0 var floatSwap func(float64, float64) (float64, float64) makeSwap(&floatSwap) fmt.Println(floatSwap(2.72, 3.14)) //返回:3.14 2.72 }