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()用来获取指针指向的源变量。