反射用来获取对象的信息,包括类型和值等等。
有时候我们希望屏蔽类型之间的差别,比如我们希望一个可以接收任何类型的函数。有时候我们又希望搞清楚这种差别,例如我们希望在函数中对不同类型的变量做不同的计算。
对于前者,我们有interface{}
类型可以屏蔽掉变量之间的类型差别。对于第二点,我们有反射。
要获得一个变量的信息,那么这些信息必须事先存储在某个地方,具体来说是运行时系统持有变量的信息,反射就是将这些变量信息暴露出来。
在golang中,变量包含两个部分:类型和值。因此反射应该至少能获得这两个信息。
在reflect
包中,类型和值分别对应着一个接口和一个结构体,下面是源代码,有很多都是注释:
/*=========================Type=========================*/
type Type interface {
// Methods applicable to all types.
// Align returns the alignment in bytes of a value of
// this type when allocated in memory.
Align() int
// FieldAlign returns the alignment in bytes of a value of
// this type when used as a field in a struct.
FieldAlign() int
// Method returns the i'th method in the type's method set.
// It panics if i is not in the range [0, NumMethod()).
//
// For a non-interface type T or *T, the returned Method's Type and Func
// fields describe a function whose first argument is the receiver.
//
// For an interface type, the returned Method's Type field gives the
// method signature, without a receiver, and the Func field is nil.
Method(int) Method
// MethodByName returns the method with that name in the type's
// method set and a boolean indicating if the method was found.
//
// For a non-interface type T or *T, the returned Method's Type and Func
// fields describe a function whose first argument is the receiver.
//
// For an interface type, the returned Method's Type field gives the
// method signature, without a receiver, and the Func field is nil.
MethodByName(string) (Method, bool)
// NumMethod returns the number of exported methods in the type's method set.
NumMethod() int
// Name returns the type's name within its package.
// It returns an empty string for unnamed types.
Name() string
// PkgPath returns a named type's package path, that is, the import path
// that uniquely identifies the package, such as "encoding/base64".
// If the type was predeclared (string, error) or unnamed (*T, struct{}, []int),
// the package path will be the empty string.
PkgPath() string
// Size returns the number of bytes needed to store
// a value of the given type; it is analogous to unsafe.Sizeof.
Size() uintptr
// String returns a string representation of the type.
// The string representation may use shortened package names
// (e.g., base64 instead of "encoding/base64") and is not
// guaranteed to be unique among types. To test for type identity,
// compare the Types directly.
String() string
// Kind returns the specific kind of this type.
Kind() Kind
// Implements reports whether the type implements the interface type u.
Implements(u Type) bool
// AssignableTo reports whether a value of the type is assignable to type u.
AssignableTo(u Type) bool
// ConvertibleTo reports whether a value of the type is convertible to type u.
ConvertibleTo(u Type) bool
// Comparable reports whether values of this type are comparable.
Comparable() bool
// Methods applicable only to some types, depending on Kind.
// The methods allowed for each kind are:
//
// Int*, Uint*, Float*, Complex*: Bits
// Array: Elem, Len
// Chan: ChanDir, Elem
// Func: In, NumIn, Out, NumOut, IsVariadic.
// Map: Key, Elem
// Ptr: Elem
// Slice: Elem
// Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField
// Bits returns the size of the type in bits.
// It panics if the type's Kind is not one of the
// sized or unsized Int, Uint, Float, or Complex kinds.
Bits() int
// ChanDir returns a channel type's direction.
// It panics if the type's Kind is not Chan.
ChanDir() ChanDir
// IsVariadic reports whether a function type's final input parameter
// is a "..." parameter. If so, t.In(t.NumIn() - 1) returns the parameter's
// implicit actual type []T.
//
// For concreteness, if t represents func(x int, y ... float64), then
//
// t.NumIn() == 2
// t.In(0) is the reflect.Type for "int"
// t.In(1) is the reflect.Type for "[]float64"
// t.IsVariadic() == true
//
// IsVariadic panics if the type's Kind is not Func.
IsVariadic() bool
// Elem returns a type's element type.
// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
Elem() Type
// Field returns a struct type's i'th field.
// It panics if the type's Kind is not Struct.
// It panics if i is not in the range [0, NumField()).
Field(i int) StructField
// FieldByIndex returns the nested field corresponding
// to the index sequence. It is equivalent to calling Field
// successively for each index i.
// It panics if the type's Kind is not Struct.
FieldByIndex(index []int) StructField
// FieldByName returns the struct field with the given name
// and a boolean indicating if the field was found.
FieldByName(name string) (StructField, bool)
// FieldByNameFunc returns the struct field with a name
// that satisfies the match function and a boolean indicating if
// the field was found.
//
// FieldByNameFunc considers the fields in the struct itself
// and then the fields in any anonymous structs, in breadth first order,
// stopping at the shallowest nesting depth containing one or more
// fields satisfying the match function. If multiple fields at that depth
// satisfy the match function, they cancel each other
// and FieldByNameFunc returns no match.
// This behavior mirrors Go's handling of name lookup in
// structs containing anonymous fields.
FieldByNameFunc(match func(string) bool) (StructField, bool)
// In returns the type of a function type's i'th input parameter.
// It panics if the type's Kind is not Func.
// It panics if i is not in the range [0, NumIn()).
In(i int) Type
// Key returns a map type's key type.
// It panics if the type's Kind is not Map.
Key() Type
// Len returns an array type's length.
// It panics if the type's Kind is not Array.
Len() int
// NumField returns a struct type's field count.
// It panics if the type's Kind is not Struct.
NumField() int
// NumIn returns a function type's input parameter count.
// It panics if the type's Kind is not Func.
NumIn() int
// NumOut returns a function type's output parameter count.
// It panics if the type's Kind is not Func.
NumOut() int
// Out returns the type of a function type's i'th output parameter.
// It panics if the type's Kind is not Func.
// It panics if i is not in the range [0, NumOut()).
Out(i int) Type
common() *rtype
uncommon() *uncommonType
}
/*=========================Value=========================*/
type Value struct {
// typ holds the type of the value represented by a Value.
typ *rtype
// Pointer-valued data or, if flagIndir is set, pointer to data.
// Valid when either flagIndir is set or typ.pointers() is true.
ptr unsafe.Pointer
// flag holds metadata about the value.
// The lowest bits are flag bits:
// - flagStickyRO: obtained via unexported not embedded field, so read-only
// - flagEmbedRO: obtained via unexported embedded field, so read-only
// - flagIndir: val holds a pointer to the data
// - flagAddr: v.CanAddr is true (implies flagIndir)
// - flagMethod: v is a method value.
// The next five bits give the Kind of the value.
// This repeats typ.Kind() except for method values.
// The remaining 23+ bits give a method number for method values.
// If flag.kind() != Func, code can assume that flagMethod is unset.
// If ifaceIndir(typ), code can assume that flagIndir is set.
flag
// A method value represents a curried method invocation
// like r.Read for some receiver r. The typ+val+flag bits describe
// the receiver r, but the flag's Kind bits say Func (methods are
// functions), and the top bits of the flag give the method number
// in r's type's method table.
}
Type
类型就是一系列函数,Value
类型上也定义了许多函数。
所以,使用反射获取变量信息的关键就是获取Type
或Value
类型的值。然后调用它们的方法可以获得变量的信息了。reflect
提供了ValueOf
和TypeOf
函数来获取这两个类型的值。比如我们有如下结构体:
type T struct {
a int `test:"a_int"`
s string `test:"s_string"`
}
func (t *T) SetA(a int) {
t.a = a
}
func (t *T) SetS(s string) {
t.s = s
}
func (t T) PrintT() {
fmt.Printf("%d : %s\n", t.a, t.s)
}
var t = T{1, "hello"}
对应的通过ValueOf
和TypeOf
函数来获取变量t
的Value和Type信息:
func main() {
value := reflect.ValueOf(t)
typ := reflect.TypeOf(t)
}
接下来的任务就是调用value
和typ
上的函数了,具体可以参照官方文档。无论是什么变量,只要抓住了这两个关键,一切就都好说了。
下面来说一些比较实在的事。
-
Value
和Type
有什么区别 ?
首先来说Value
上的方法都是针对值的操作,比如获取、设置、取地址等。而Type
上的方法多跟类型信息有关,比如变量的名字、包路径、结构体字段的标签等。 -
Value
和Type
在获取结构体方法上的区别 ?
在结构体上,通过Value
和Type
都可以获得结构体的方法。如下:f1 := value.MethodByName("PrintT") f2, ok := tp.MethodByName("PrintT")
那么这两种方式获取到的方法有什么区别吗?看似一样,实则不一样。
首先需要知道方法和函数的区别。我们把定义在结构体上的函数叫做方法,它们的本质都是函数,只不过在调用方法时,会自动将方法的调用者(结构体)作为参数传入方法,也就是说调用方法时会多传入一个参数。
上面f1
是叫做方法,是定义在结构体t
上的方法,换句话说,调用f1
会自动将t
传入f1
,就和调用t.PrintT()
是一样的。
而f2
叫做函数,它只是一个普通的函数,是接收一个T
类型值为参数的函数,与t
无关。相当于是这样一个函数:func PrintT(t T)
。
那么,如果想分别通过f1
和f2
调用PrintT
函数,我们必须按如下的方法去做:f1.Call(nil) f2.Call([]reflect.Value{value})
value
是通过t
生成的,在调用f2.Call
时,我们必须将value
作为参数传递进去,否则将会因参数不匹配而出错。结果如下:1 : hello 1 : hello
为了验证
f2
现在是一个普通函数,我们可以将另一个结构体作为参数传递给f2.Call
,看看输出会是什么。f2.Call([]reflect.Value{reflect.ValueOf(T{2, "word"})})
输出如下:
2 : word
如果按照这样的方式去调用
f1
会怎么样呢?
答案是会出错。因为t
已经作为参数传递给f1
了,再传一个参数就会导致调用PrintT
时带入了两个参数,当然会出错了。 -
值接收者与指针接收者
我们知道,值并不拥有指针接收者的方法,这点很重要。
如果我们想通过反射调用SetA
函数该怎么做呢?如果还和调用PrintT
函数一样铁定是不行的。
因为SetA
是定义在*T
上的方法,而我们的value
和typ
都是从值变量t
上获得的。换句话说,value
和typ
上并不包含*T
的信息,也就是它俩存储的信息中并没有SetA
和SetS
这两个函数,自然也就无法通过value
和typ
来调用SetA
和SetS
函数了。
为了调用SetA
和SetS
函数,我们需要将重新从&t
上获得Value
和Type
,然后再获取函数SetA
,如下:value := reflect.ValueOf(&t) tp := reflect.TypeOf(&t) f1 := value.MethodByName("SetA") f2, _ := tp.MethodByName("SetA")
接下来就是参数了,
SetA
需要一个int
值作为参数,但是Call
函数只接受[]reflect.Value
类型的参数,于是我们需要参数包装一下:param1 := reflect.ValueOf(5) param2 := reflect.ValueOf(7)
接下来让我们来调用
SetA
函数:t.PrintT() f1.Call([]reflect.Value{param1}) t.PrintT() f2.Func.Call([]reflect.Value{value, param2}) t.PrintT()
还是要注意,此时
f2
是一个普通函数,因此需要将value
(实际上就是&t
)作为参数带入,并且还要是第一个参数,切记切记,放第二位都不行。
结果如下:1 : hello 5 : hello 7 : hello
至于调用
SetS
函数就和调用SetA
函数是一样的了。这是关于调用函数的,如果是要获取结构体的字段,还是得用值类型,千万不能用指针类型。
-
获取结构体的标签
结构体的标签也属于类型信息,因此必须通过Type
来获取。在reflect
包中有这样一个结构体:type StructField struct { // Name is the field name. // PkgPath is the package path that qualifies a lower case (unexported) // field name. It is empty for upper case (exported) field names. // See http://golang.org/ref/spec#Uniqueness_of_identifiers Name string PkgPath string Type Type // field type Tag StructTag // field tag string Offset uintptr // offset within struct, in bytes Index []int // index sequence for Type.FieldByIndex Anonymous bool // is an embedded field }
结构体的字段信息都包含在这个
StructField
结构体内,其中Tag
字段就是标签。类型StructTag
是string
类型的别名,在该类型上定义了两个方法:Get
和LookUp
。它们的唯一区别是LookUp
函数比Get
函数多返回了一个bool
值,来标识当返回空字符串时,究竟是标签不存在还是本身就是空。
话说回来,如何获取StructField
结构体呢?凡是Type
接口中以Field
开头的函数都会返回这样一个结构体,一共是4个:Field(i int) StructField FieldByIndex(index []int) StructField FieldByName(name string) (StructField, bool) FieldByNameFunc(match func(string) bool) (StructField, bool)
以上就是使用反射时一些需要注意的地方,记住两个关键:Value
和Type
。