reflect 初识 | 青训营笔记

134 阅读7分钟

reflect

go 语言标准库中的 reflect 包为我们提供了一种可以在运行时操作任意类型对象的能力。通过 reflect 包我们可以查看一个接口变量的具体类型,修改结构体的某一字段等等

reflect.Value 和 reflect.Type

在 go 语言中,任何接口都由两部分组成:接口的具体类型,以及具体类型对应的值。例如 i := 1 ,将 i 赋值给一个 interface() ,这里这个空接口的 reflect.Value 便是1,reflect.Type 便是 int

那我们如何得到 reflect.Valuereflect.Type 呢? 标准库为我们提供了两个函数 reflect.ValueOfreflect.TypeOf 分别获取任意对象的 reflect.Valuereflect.Type

func main(){
    i := 1
    iv := reflect.ValueOf(i)
    it := reflect.TypeOf(i)
    fmt.Println("i value: ",iv,"\ni type: "it)
}

运行上面的代码,会在终端输出

i value: 1
i type: int

reflect.Value

reflect.Value 是一个结构体

type Value struct{
    typ *rtype
    
    ptr unsafe.Pointer
    
    flag 
}

可以看到,它的字段都是不可导出的,使用我们想要获取 value 的属性只能使用它所包含的方法。 大致可以分为三类: 一类用于获取和修改对应的值;一类和 struct 类型的字段有关,用于获取对应的字段;一类和类型上的方法集有关,用于获取对应的方法。

//针对具体类型的系列方法

//以下是用于获取对应的值

Bool

Bytes

Complex

Float

Int

String

Uint

CanSet //是否可以修改对应的值

以下是用于修改对应的值

Set

SetBool

SetBytes

SetComplex

SetFloat

SetInt

SetString

Elem //获取指针指向的值,一般用于修改对应的值

//以下Field系列方法用于获取struct类型中的字段

Field

FieldByIndex

FieldByName

FieldByNameFunc

Interface //获取对应的原始类型

IsNil //值是否为nil

IsZero //值是否是零值

Kind //获取对应的类型类别,比如Array、Slice、Map等

//获取对应的方法

Method

MethodByName

NumField //获取struct类型中字段的数量

NumMethod//类型上方法集的数量

Type//获取对应的reflect.Type

具体使用建议自己去试。

reflect.Type

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,
   // and only exported methods are accessible.
   //
   // For an interface type, the returned Method's Type field gives the
   // method signature, without a receiver, and the Func field is nil.
   //
   // Methods are sorted in lexicographic order.
   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 methods accessible using Method.
   //
   // For a non-interface type, it returns the number of exported methods.
   //
   // For an interface type, it returns the number of exported and unexported methods.
   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 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
}

其中几个特有的方法如下:

  1. Implements 方法用于判断是否实现了接口 u;
  2. AssignableTo 方法用于判断是否可以赋值给类型 u,其实就是是否可以使用 =,即赋值运算符;
  3. ConvertibleTo 方法用于判断是否可以转换成类型 u,其实就是是否可以进行类型转换;
  4. Comparable 方法用于判断该类型是否是可比较的,其实就是是否可以使用关系运算符进行比较。

反射定律

计算机学中,反射式编程(英语:reflective programming)或反射(英语:reflection),是指计算机程序运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力。[1]用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。 ----维基百科

go 语言作者在博客上总结了反射三定律:

  1. 任何接口值 interface{} 都可以反射出反射对象,也就是 reflect.Value 和 reflect.Type,通过函数 reflect.ValueOf 和 reflect.TypeOf 获得。
  2. 反射对象也可以还原为 interface{} 变量,也就是第 1 条定律的可逆性,通过 reflect.Value 结构体的 Interface 方法获得。
  3. 要修改反射的对象,该值必须可设置,也就是可寻址。

总结

想要进行与值相关的操作,使用 reflect.Value ,比如获取变量的值、修改变量的值。对应的,与类型相关的使用 reflect.Type ,比如获取结构体内的字段、类型拥有的方法集。