go进阶-反射
定义
反射是程序在运行时能访问、修改自身状态或者行为的一种能力。
也就是程序在运行的时候能够得到并修改变量的值。
demo:
import (
"fmt"
"reflect"
)
func main() {
// interface{}用于输入不知道具体类型的,伪泛型
rv := []interface{}{"hi", 1, func() {}}
for _, v := range rv {
switch v := reflect.ValueOf(v); v.Kind() {
case reflect.String:
fmt.Println(v.String())
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
fmt.Println(v.Int())
default:
fmt.Printf("unhandled type %v\n", v.Kind())
}
}
}
// hi
// 1
// unhandled type func
具体含义在下文中说。
Golang reflect
反射库中,最核心的就是refelct.Type
(类型信息)和reflect.Value
(值信息)类型。
type Blog struct {
name string
site string
}
func main() {
blog := Blog{
name: "hqinglau的博客",
site: "orzlinux.cn",
}
tpe := reflect.TypeOf(blog)
value := reflect.ValueOf(blog)
fmt.Println(tpe) //main.Blog, 也就是连package都知道了
fmt.Println(value) //{hqinglau的博客 orzlinux.cn}
}
TypeOf源码
func TypeOf(i any) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
其中:
Unsafe.Pointer
获取任意类型的指针值。
emptyInterface
强制类型转换
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
// rtype实现了Type类型的所有接口方法,可以作为Type返回
type rtype struct {
size uintptr
ptrdata uintptr // number of bytes in the type that can contain pointers
hash uint32 // hash of type; avoids computation in hash tables
tflag tflag // extra type information flags
align uint8 // alignment of variable with this type
fieldAlign uint8 // alignment of struct field with this type
kind uint8 // enumeration for C
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte // garbage collection data
str nameOff // string form
ptrToThis typeOff // type for pointer to this type, may be zero
}
而Type是一个接口:
type Type interface {
// 返回对齐后的字节数
Align() int
// struct类型对齐后的字节数
FieldAlign() int
// Method returns the i'th method in the type's method set.
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.
MethodByName(string) (Method, bool)
// NumMethod returns the number of methods accessible using Method.
//
// Note that NumMethod counts unexported methods only for interface types.
NumMethod() int
// Name returns the type's name within its package for a defined type.
// For other (non-defined) types it returns the empty string.
Name() string
// PkgPath returns a defined 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 not defined (*T, struct{},
// []int, or A where A is an alias for a non-defined type), 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.
// Even if ConvertibleTo returns true, the conversion may still panic.
// For example, a slice of type []T is convertible to *[N]T,
// but the conversion will panic if its length is less than N.
ConvertibleTo(u Type) bool
// Comparable reports whether values of this type are comparable.
// Even if Comparable returns true, the comparison may still panic.
// For example, values of interface type are comparable,
// but the comparison will panic if their dynamic type is not 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
// Pointer: 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, Pointer, 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 embedded 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 embedded 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(i int) Type
common() *rtype
uncommon() *uncommonType
}
toType
转换为可供外部使用的Type类型。
ValueOf源码
type any = interface{}
func ValueOf(i any) Value {
if i == nil {
return Value{}
}
// TODO: Maybe allow contents of a Value to live on the stack.
// For now we make the contents always escape to the heap. It
// makes life easier in a few places (see chanrecv/mapassign
// comment below).
escapes(i)
return unpackEface(i)
}
// unpackEface converts the empty interface i to a Value.
func unpackEface(i any) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
// NOTE: don't read e.word until we know whether it is really a pointer or not.
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
}
先用escapes
让变量i
逃逸到堆上,然后强制转化为emptyInterface
类型,然后把所需的信息组装成Value
类型返回。
set
按照之前的解释,通过ValueOf
之后,栈上的应该到堆上,看下面的代码:
func main() {
i := 1.23
v := reflect.ValueOf(&i)
v.Elem().SetFloat(6.66)
fmt.Printf("value: %v", i) //value: 6.66
}
// admin@admindeMacBook-Pro gosrc % go build -gcflags '-m' test.go
// # command-line-arguments
// ./test.go:10:22: inlining call to reflect.ValueOf
// ./test.go:10:22: inlining call to reflect.escapes
// ./test.go:10:22: inlining call to reflect.unpackEface
// ./test.go:10:22: inlining call to reflect.(*rtype).Kind
// ./test.go:10:22: inlining call to reflect.ifaceIndir
// ./test.go:12:12: inlining call to fmt.Printf
// ./test.go:9:2: moved to heap: i
// ./test.go:12:12: ... argument does not escape
// ./test.go:12:13: i escapes to heap
最后有一个:
// ./test.go:9:2: moved to heap: i
i确实到了堆上。
如果设置的类型不对应,会panic:
func main() {
i := 1.23
v := reflect.ValueOf(&i)
v.Elem().SetInt(11)
fmt.Printf("value: %v", i)
//panic: reflect: call of reflect.Value.SetInt on float64 Value
}
在设置的时候,会判断类型:
// SetInt sets v's underlying value to x.
// It panics if v's Kind is not Int, Int8, Int16, Int32, or Int64, or if CanSet() is false.
func (v Value) SetInt(x int64) {
v.mustBeAssignable()
switch k := v.kind(); k {
default:
panic(&ValueError{"reflect.Value.SetInt", v.kind()})
case Int:
*(*int)(v.ptr) = int(x)
case Int8:
*(*int8)(v.ptr) = int8(x)
case Int16:
*(*int16)(v.ptr) = int16(x)
case Int32:
*(*int32)(v.ptr) = int32(x)
case Int64:
*(*int64)(v.ptr) = x
}
}
还有set方法如下:
// Set assigns x to the value v.
// It panics if CanSet returns false.
// As in Go, x's value must be assignable to v's type.
func (v Value) Set(x Value) {
v.mustBeAssignable()
x.mustBeExported() // do not let unexported x leak
var target unsafe.Pointer
if v.kind() == Interface {
target = v.ptr
}
x = x.assignTo("reflect.Set", v.typ, target)
if x.flag&flagIndir != 0 {
if x.ptr == unsafe.Pointer(&zeroVal[0]) {
typedmemclr(v.typ, v.ptr)
} else {
typedmemmove(v.typ, v.ptr, x.ptr)
}
} else {
*(*unsafe.Pointer)(v.ptr) = x.ptr
}
}
反射三大定律
第一定律
反射可以从接口值获取反射对象。
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
}
第二定律
反射可以从反射对象获取接口值。
func main() {
vo := reflect.ValueOf(3.14) //var vo reflect.Value
vf := vo.Interface().(float64)
// 利用Interface方法获取接口值,然后强制转化为float类型
fmt.Printf("vf: %T\n", vf) //vf: float64
}
第三定律
要哦修改反射对象,该值必须可以修改。
func main() {
i := 1.23
v := reflect.ValueOf(&i)
v.Elem().SetFloat(6.66)
fmt.Printf("value: %v", i) //value: 6.66
}
两个问题:
为何传入i的指针?为何v设置之前还要Elem()
一下?
go
的函数调用传递是值传递,如果不传指针,无法变动原对象值。
Elem()
用来获取指针指向的源变量。