Go反射

反射用来获取对象的信息,包括类型和值等等。

有时候我们希望屏蔽类型之间的差别,比如我们希望一个可以接收任何类型的函数。有时候我们又希望搞清楚这种差别,例如我们希望在函数中对不同类型的变量做不同的计算。

对于前者,我们有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类型上也定义了许多函数。

所以,使用反射获取变量信息的关键就是获取TypeValue类型的值。然后调用它们的方法可以获得变量的信息了。reflect提供了ValueOfTypeOf函数来获取这两个类型的值。比如我们有如下结构体:

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"}

对应的通过ValueOfTypeOf函数来获取变量t的Value和Type信息:

func main() {
    value := reflect.ValueOf(t)
    typ := reflect.TypeOf(t)
}

接下来的任务就是调用valuetyp上的函数了,具体可以参照官方文档。无论是什么变量,只要抓住了这两个关键,一切就都好说了。

下面来说一些比较实在的事。

  1. ValueType有什么区别 ?
    首先来说Value上的方法都是针对值的操作,比如获取、设置、取地址等。而Type上的方法多跟类型信息有关,比如变量的名字、包路径、结构体字段的标签等。

  2. ValueType在获取结构体方法上的区别 ?
    在结构体上,通过ValueType都可以获得结构体的方法。如下:

    f1 := value.MethodByName("PrintT")
    f2, ok := tp.MethodByName("PrintT")
    

    那么这两种方式获取到的方法有什么区别吗?看似一样,实则不一样。
    首先需要知道方法和函数的区别。我们把定义在结构体上的函数叫做方法,它们的本质都是函数,只不过在调用方法时,会自动将方法的调用者(结构体)作为参数传入方法,也就是说调用方法时会多传入一个参数。
    上面f1是叫做方法,是定义在结构体t上的方法,换句话说,调用f1会自动将t传入f1,就和调用t.PrintT()是一样的。
    f2叫做函数,它只是一个普通的函数,是接收一个T类型值为参数的函数,与t无关。相当于是这样一个函数:func PrintT(t T)
    那么,如果想分别通过f1f2调用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时带入了两个参数,当然会出错了。

  3. 值接收者与指针接收者
    我们知道,值并不拥有指针接收者的方法,这点很重要。
    如果我们想通过反射调用SetA函数该怎么做呢?如果还和调用PrintT函数一样铁定是不行的。
    因为SetA是定义在*T上的方法,而我们的valuetyp都是从值变量t上获得的。换句话说,valuetyp上并不包含*T的信息,也就是它俩存储的信息中并没有SetASetS这两个函数,自然也就无法通过valuetyp来调用SetASetS函数了。
    为了调用SetASetS函数,我们需要将重新从&t上获得ValueType,然后再获取函数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函数是一样的了。

    这是关于调用函数的,如果是要获取结构体的字段,还是得用值类型,千万不能用指针类型。
  4. 获取结构体的标签
    结构体的标签也属于类型信息,因此必须通过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字段就是标签。类型StructTagstring类型的别名,在该类型上定义了两个方法:GetLookUp。它们的唯一区别是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)
    

以上就是使用反射时一些需要注意的地方,记住两个关键:ValueType

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值