Golang反射编程指南

17 阅读18分钟

主要内容转载自:mp.weixin.qq.com/s/9OjvKLEei… (为方便个人翻阅),也加了一些个人的理解和内容。

0. 引言

反射是编程语言的高级特性,允许程序在运行时访问变量的类型信息和值信息,如:类型名、类型有哪些方法、结构体有哪些字段、函数/方法的入参和出参、修改变量值、调用变量的方法等等。
反射使得程序具有更大的灵活性和可扩展性,但也有缺点:性能问题、代码可读性和可维护性问题。

1. Go语言反射基础

Go语言内置的reflect包是所有Go反射编程的基础API,是进行Go反射编程的必经之路。

1.1 Type和Value

在reflect包中,Type和Value是两个非常重要的概念,它们分别表示了反射世界中的类型信息和值信息

Type表示一个类型的元信息,它包含了类型的名称、大小、方法集合等信息。在反射编程中,我们可以使用TypeOf函数来获取一个值的类型信息。

Value表示一个值的信息,它包含了值的类型、值本身以及对值进行操作的方法集合等信息。在反射中,我们可以使用ValueOf函数来获取一个值的Value信息。

reflect包的TypeOfValueOf两个函数是进入反射世界的基本入口。

1.2 获取类型信息(TypeOf)

TypeOf函数的签名如下:

func TypeOf(i any) Type

示例:

func main() {  
    s := "hello, world!"  
    t := reflect.TypeOf(s)  
    fmt.Println(t.Name()) // string  
}

1.3 获取值信息(ValueOf)

func ValueOf(i any) Value

示例:

func main() {  
    i := 42  
    v := reflect.ValueOf(i)  
    fmt.Println(v.Int()) // 42  
}

2.获取类型信息详解

reflect.Type实质上是一个接口类型,它封装了reflect可以提供的类型信息的所有方法(Go 1.20版本中的reflect.Type):

// $GOROOT/src/reflect/type.go

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
}

我们看到这是一个“超级接口”,严格来说并不符合Go接口设计的惯例。

注:Go崇尚小接口。以Type接口为例,可以对Type接口做进一步分解,分解成若干内聚的小接口,然后将Type看成小接口的组合。

对于不同类型,Type接口的有些方法是冗余的,比如像上面的NumField、NumIn和NumOut方法对于一个int变量的类型信息来说就毫无意义。Type类型的注释中也提到:“Not all methods apply to all kinds of types”。

一旦通过TypeOf进入反射世界,拿到Type类型变量,那么我们就可以基于上述方法“翻看”类型的各种信息了。

对于像int、float64、string这样的基本类型来说,其类型信息的检视没有太多可说的。但对于其他类型,诸如复合类型、指针类型、函数类型等,还是有一些可聊聊的,我们下面逐一简单地看一下。

2.1 复合类型

2.1.1 数组类型

在Go中,数组类型是一种典型的复合类型,它有若干属性,包括数组长度、数组是否支持可比较、数组元素的类型等,看下面示例:

import (
    "fmt"
    "reflect"
)

func main() { 
    arr := [5]int{12345}
    typ := reflect.TypeOf(arr)
    fmt.Println(typ.Kind())       // array
    fmt.Println(typ.Len())        // 5
    fmt.Println(typ.Comparable()) // true

    elemTyp := typ.Elem()
    fmt.Println(elemTyp.Kind())       // int
    fmt.Println(elemTyp.Comparable()) // true
}

注:通过类型信息无法间接得到值信息,反之不然,稍后系统说明reflect.Value时会提到。

Kind是什么

在上面例子,我们输出了arr这个数组类型变量的Kind信息。什么是Kind信息呢?reflect包中是如此定义的:

// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint

const (
    Invalid Kind = iota
    Bool
    Int
    Int8
    Int16
    Int32
    Int64
    Uint
    Uint8
    Uint16
    Uint32
    Uint64
    Uintptr
    Float32
    Float64
    Complex64
    Complex128
    Array
    Chan
    Func
    Interface
    Map
    Pointer
    Slice
    String
    Struct
    UnsafePointer
)

Type是具体类型,而Kind是类型的种类。比如:切片类型具体可以是int切片、string切片......

以两个数组类型为例:

var arr1 [10]string
var arr2 [8]int

这两个数组类型的类型分别是[10]string和[8]int,但它们在反射世界的reflect.Type的Kind信息却都为Array。

再比如下面两个指针类型:

var p1 *float64
var p2 *MyFoo

这两个指针类型的类型分别是*float64*MyFoo,但它们在反射世界的reflect.Type的Kind信息却都为Pointer

Kind信息可以帮助开发人员在反射世界中区分类型,以对不同类型作不同的处理。比如对于Kind为Int的reflect.Type,你不能使用其Len()方法,否则会panic;但对于Kind为Array的则可以。开发人员使用反射提供的Kind信息可以处理不同类型的数据。

2.1.2 切片类型

在Go中切片是动态数组,可灵活、透明的扩容,多数情况下切片都能替代数组完成任务。在反射世界中通过reflect.Type我们可以获取切片类型的信息,包括元素类型等。下面是一个示例:

func main() { 
    s := make([]int510)
    typ := reflect.TypeOf(s)
    fmt.Println(typ.Kind()) // slice
    fmt.Println(typ.Elem()) // int
}

如果我们使用上面的变量typ调用Type类型的Len和Cap方法会发生什么呢?在运行时,你将得到类似"panic: reflect: Len of non-array type []int"的报错!

那么问题来了!切片长度、容量到底是否是slice type的信息范畴呢? 我们来看一个例子:

var a = make([]int510)
var b = make([]int78)

变量a和b的类型都是[]int。显然长度、容量等并不在切片类型的范畴,而是与切片变量值绑定的,下面的示例印证了这一点:

func main() {
    s := make([]int510)
    val := reflect.ValueOf(s)
    fmt.Println(val.Len()) // 5
    fmt.Println(val.Cap()) // 10
}   

我们获取了切片变量s的reflect.Value信息,通过Value我们得到了变量s的长度和容量信息。

注意:切片的长度和容量属于值信息。

2.1.3 结构体类型

结构体类型是与反射联合使用的重要类型,下面代码展示了如何通过reflect.Type获取结构体类型的相关信息:

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
    gender  string
}

func (p Person) SayHello() {
    fmt.Printf("Hello, my name is %s, and I'm %d years old.\n", p.Name, p.Age)
}
func (p Person) unexportedMethod() {
}

func main() {
    p := Person{Name: "Tom", Age: 20, gender: "male"}
    typ := reflect.TypeOf(p)
    fmt.Println(typ.Kind())                   // struct
    fmt.Println(typ.NumField())               // 2
    fmt.Println(typ.Field(0).Name)            // Name
    fmt.Println(typ.Field(0).Type)            // string
    fmt.Println(typ.Field(0).Tag)             // json:"name"
    fmt.Println(typ.Field(1).Name)            // Age
    fmt.Println(typ.Field(1).Type)            // int
    fmt.Println(typ.Field(1).Tag)             // json:"age"
    fmt.Println(typ.Field(2).Name)            // gender
    fmt.Println(typ.Method(0).Name)           // SayHello
    fmt.Println(typ.Method(0).Type)           // func(main.Person)
    fmt.Println(typ.Method(0).Func)           // 0x109b6e0
    fmt.Println(typ.MethodByName("SayHello")) // {SayHello func(main.Person)}
    fmt.Println(typ.MethodByName("unexportedMethod")) // {  <nil> <invalid Value> 0} false
}

从上面例子可以看到,我们可以使用NumField、Field、NumMethod、Method和MethodByName等方法获取结构体的字段信息和方法信息。其中,Field方法返回的是StructField类型的值,包含了字段的名称、类型、标签等信息;Method方法返回的是Method类型的值,包含了方法的名称、类型和函数值等信息。

不过要注意:通过Type可以得到结构体中非导出字段的信息(如上面示例中的gender),但无法获取结构体类型的非导出方法信息(如上面示例中的unexportedMethod)

2.1.4 channel类型

channel是Go特有的类型,channel与切片很像,它的类型信息包括元素类型、chan读写特性,但channel的长度与容量与channel变量是绑定的,看下面示例:

func main() {
    ch := make(chan<- int10)
    ch <- 1
    ch <- 2
    typ := reflect.TypeOf(ch)
    fmt.Println(typ.Kind())      // chan
    fmt.Println(typ.Elem())      // int
    fmt.Println(typ.ChanDir())   // chan<-

    fmt.Println(reflect.ValueOf(ch).Len()) // 2
    fmt.Println(reflect.ValueOf(ch).Cap()) // 10
}

2.1.5 map类型

map是go常用的内置的复合类型,它是一个无序键值对的集合,通过反射可以获取其键和值的类型信息:

func main() {
    m := map[string]int{"a"1"b"2"c"3}
    typ := reflect.TypeOf(m)
    fmt.Println(typ.Kind()) // map        
    fmt.Println(typ.Key())  // string     
    fmt.Println(typ.Elem()) // int        

    fmt.Println(reflect.ValueOf(m).Len()) // 3
}

我们看到,和切片一样,map变量的长度信息是与map变量的Value绑定的,另外要注意:map变量不能获取容量信息

2.2 指针类型

指针类型是一个大类,通过Type可以获得指针的kind和其指向的变量的类型信息:

func main() {
    i := 10
    p := &i
    typ := reflect.TypeOf(p)
    fmt.Println(typ.Kind())                      // ptr
    fmt.Println(typ.Elem())                      // int
}

2.3 接口类型

接口即契约。在Go中非作为约束的接口类型本质就是一个方法集合,通过reflect.Type可以获得接口类型的这些信息:

type Animal interface {
    Speak() string
}

type Cat struct{}

func (c Cat) Speak() string {
    return "Meow"
}

func main() {
    var a Animal = Cat{}
    typ := reflect.TypeOf(a)
    fmt.Println(typ.Kind())         // interface
    fmt.Println(typ.NumMethod())    // 1
    fmt.Println(typ.Method(0).Name) // Speak
    fmt.Println(typ.Method(0).Type) // func(main.Animal) string
}

2.4 函数类型

函数在Go中是一等公民,我们可以将其像普通int类型那样去使用,传参、赋值、做返回值都是ok的。下面是通过Type获取函数类型信息的示例:

func foo(a, b int, c *int) (intbool) { 
    *c = a + b 
    return *c, true
}

func main() { 
    typ := reflect.TypeOf(foo)
    fmt.Println(typ.Kind())                      // func
    fmt.Println(typ.NumIn())                     // 3
    fmt.Println(typ.In(0), typ.In(1), typ.In(2)) // int int *int
    fmt.Println(typ.NumOut())                    // 2
    fmt.Println(typ.Out(0))                      // int
    fmt.Println(typ.Out(1))                      // bool
}

我们看到和其他类型不同,函数支持NumOut、NumIn、Out等方法。其中In是输出参数的集合,Out则是返回值参数的集合。

3.获取值和修改值信息

掌握了如何在反射世界获取一个变量的类型信息后,我们再来看看如何在反射世界获取并修改一个变量的值信息。

注:并不是所有变量都可以修改值的,可以使用Value的CanSet方法判断值是否可以设置。

// 测试通过反射修改变量值
func main() {
   var x float64 = 3.14
   valueOfX := reflect.ValueOf(x)
   // 获取值
   fmt.Println("valueOfX.value:", valueOfX.Interface().(float64)) // valueOfX.value: 3.14
   fmt.Println("CanSet:", valueOfX.CanSet()) // CanSet: false
   //valueOfX.SetFloat(6.66) // panic: reflect: reflect.Value.SetFloat using unaddressable value。因为valueOfX是x变量副本的值,并不能改变原本的x变量值,类似函数想修改函数外的变量值时必须传变量的指针

   valueOfXPtr := reflect.ValueOf(&x)
   fmt.Println("valueOfXPtr.value:", valueOfXPtr.Interface()) // valueOfXPtr.value: 0xc000018088
   fmt.Println("CanSet:", valueOfXPtr.CanSet()) // CanSet: false
   //valueOfXPtr.SetFloat(7.77) // panic: reflect: reflect.Value.SetFloat using unaddressable value

   elem := valueOfXPtr.Elem()
   fmt.Println("CanSet:", elem.CanSet()) // CanSet: true
   elem.SetFloat(8.88)
   fmt.Println("x:", x) // x: 8.88
   fmt.Println("elem.Value:", elem.Interface().(float64)) // elem.Value: 8.88
   elem.Set(reflect.ValueOf(9.99))
   fmt.Println("x:", x) // x: 9.99
   fmt.Println("elem.Value:", elem.Interface().(float64)) // elem.Value: 9.99
}

4. ValueElem()IsNil()IsValid()

Elem()方法:

// Elem returns the value that the interface v contains
// or that the pointer v points to.
// It panics if v's Kind is not Interface or Pointer.
// It returns the zero Value if v is nil.
func (v Value) Elem() Value {
   k := v.kind()
   switch k {
   case Interface:
      var eface any
      if v.typ.NumMethod() == 0 {
         eface = *(*any)(v.ptr)
      } else {
         eface = (any)(*(*interface {
            M()
         })(v.ptr))
      }
      x := unpackEface(eface)
      if x.flag != 0 {
         x.flag |= v.flag.ro()
      }
      return x
   case Pointer:
      ptr := v.ptr
      if v.flag&flagIndir != 0 {
         if ifaceIndir(v.typ) {
            if !verifyNotInHeapPtr(*(*uintptr)(ptr)) {
               panic("reflect: reflect.Value.Elem on an invalid notinheap pointer")
            }
         }
         ptr = *(*unsafe.Pointer)(ptr)
      }
      // The returned value's address is v's value.
      if ptr == nil {
         return Value{}
      }
      tt := (*ptrType)(unsafe.Pointer(v.typ))
      typ := tt.elem
      fl := v.flag&flagRO | flagIndir | flagAddr
      fl |= flag(typ.Kind())
      return Value{typ, ptr, fl}
   }
   panic(&ValueError{"reflect.Value.Elem", v.kind()})
}

注释:
调用Elem方法时Value的Kind必须是接口或指针,否则会panic;Elem方法会返回接口包含的值或指针指向的值;如果Value的值是nil(如:接口值是nil、指针是nil),则返回「零值」——Value{}


IsNil方法:

// IsNil reports whether its argument v is nil. The argument must be
// a chan, func, interface, map, pointer, or slice value; if it is
// not, IsNil panics. Note that IsNil is not always equivalent to a
// regular comparison with nil in Go. For example, if v was created
// by calling ValueOf with an uninitialized interface variable i,
// i==nil will be true but v.IsNil will panic as v will be the zero
// Value.
func (v Value) IsNil() bool {
   k := v.kind()
   switch k {
   case Chan, Func, Map, Pointer, UnsafePointer:
      if v.flag&flagMethod != 0 {
         return false
      }
      ptr := v.ptr
      if v.flag&flagIndir != 0 {
         ptr = *(*unsafe.Pointer)(ptr)
      }
      return ptr == nil
   case Interface, Slice:
      // Both interface and slice are nil if first word is 0.
      // Both are always bigger than a word; assume flagIndir.
      return *(*unsafe.Pointer)(v.ptr) == nil
   }
   panic(&ValueError{"reflect.Value.IsNil", v.kind()})
}

注释:
IsNil方法返回Value是否是nil
调用IsNil方法时,Value的Kind必须是chanfunc、接口、map、指针或切片,否则会panic。
【注意】IsNil并不总是等同于Go中与nil的常规比较,比如:如果 v 是通过使用未初始化的接口变量 i 调用 ValueOf 创建的(此时ValueOf方法会返回零值),则 i==nil 将为 true,但 v.IsNil() 将崩溃,因为 v 将是零值。


IsValid方法:

// IsValid reports whether v represents a value.
// It returns false if v is the zero Value.
// If IsValid returns false, all other methods except String panic.
// Most functions and methods never return an invalid Value.
// If one does, its documentation states the conditions explicitly.
func (v Value) IsValid() bool {
   return v.flag != 0
}

注释:
IsValid方法返回Value是否代表一个值,如果Value是零值,则返回false
IsValid返回false,则除String方法之外的其他所有方法都会panic。
大多数函数或方法永远不会返回无效的Value,如果会返回,则文档中会明确说明条件。


测试示例:


type T1Interface interface {
   T1Method() string
}

type T1Struct struct {
}

func (t *T1Struct) T1Method() string {
   return "T1Struct"
}

func main() {
   // 未初始化的接口变量
   var i1 interface{}
   fmt.Println(i1)        // <nil>
   fmt.Println(i1 == nil) // true
   i1V := reflect.ValueOf(i1)
   fmt.Println(i1V.IsValid()) // false
   //fmt.Println(i1V.IsNil()) // 会panic

   var i2 T1Interface
   fmt.Println(i2)        // <nil>
   fmt.Println(i2 == nil) // true
   i2V := reflect.ValueOf(i2)
   fmt.Println(i2V.IsValid()) // false
   //fmt.Println(i2V.IsNil()) // 会panic

   // 接口初始化
   var i3 T1Interface
   i3 = &T1Struct{}
   fmt.Println(i3 == nil) // false
   i3V := reflect.ValueOf(i3)
   fmt.Println(i3V.Type())           // *main.T1Struct
   fmt.Println(i3V.IsValid())        // true
   fmt.Println(i3V.Kind())           // ptr
   fmt.Println(i3V.Elem().IsValid()) // true
   fmt.Println(i3V.Elem().Kind())    // struct

   // 空指针
   var p1 *int
   p1V := reflect.ValueOf(p1)
   fmt.Println(p1V.IsValid()) // true
   fmt.Println(p1V.Kind())    // ptr
   fmt.Println(p1V.Elem())    // <invalid reflect.Value>。因为Value是空指针
   fmt.Println(p1V.IsNil())   // true
}

5. 调用函数和方法

通过反射我们可以在反射世界调用函数,也可以调用特定类型的变量的方法。

下面是一个通过reflect.Value调用函数的简单例子:

func add(a, b int) int {
    return a + b
}

func main() {
    // 获取函数类型变量
    val := reflect.ValueOf(add)
    // 准备函数参数
    args := []reflect.Value{reflect.ValueOf(1), reflect.ValueOf(2)}
    // 调用函数
    result := val.Call(args)
    fmt.Println(result[0].Int()) // 输出:3
}

从示例看到,我们通过Value的Call方法来调用函数add。add有两个入参,我们不能直接传入int类型,因为这是在反射世界,我们要用反射世界的“专用参数”,即ValueOf后的值。Call的结果就是反射世界的返回值的Value形式,通过Value.Int方法可以还原反射世界的Value为int。

注:通过reflect.Type无法调用函数和方法。

方法的调用与函数调用类似,下面是一个例子:

type Rectangle struct {
    Width  float64
    Height float64
}

func (r Rectangle) Area(factor float64float64 {
    return r.Width * r.Height * factor
}

func main() {
    r := Rectangle{Width: 10, Height: 5}
    val := reflect.ValueOf(r)
    method := val.MethodByName("Area")
    args := []reflect.Value{reflect.ValueOf(1.5)}
    result := method.Call(args)
    fmt.Println(result[0].Float()) // 输出:75
}

通过MethodByName获取反射世界的method value,然后同样是通过Call方法实现方法Area的调用。

注:reflect目前不支持对非导出方法的调用。

总结:函数和方法的调用必须通过Value来调用,和值有关!

6. 动态创建类型实例

reflect更为强大的功能是可以在运行时动态创建各种类型的实例。下面是在反射世界动态创建各种类型实例的示例。

6.1 基本类型

下面以int、float64和string为例演示一下如何通过reflect在运行时动态创建基本类型的实例。

New方法:

// New returns a Value representing a pointer to a new zero value
// for the specified type. That is, the returned Value's Type is PointerTo(typ).
func New(typ Type) Value

New返回的是指向指定类型的新零值的指针

  • 创建int类型实例
func main() {
    val := reflect.New(reflect.TypeOf(0))
    val.Elem().SetInt(42)
    fmt.Println(val.Elem().Int()) // 输出:42
}
  • 创建float64类型实例
func main() {
    val := reflect.New(reflect.TypeOf(0.0))
    val.Elem().SetFloat(3.14)
    fmt.Println(val.Elem().Float()) // 输出:3.14
}
  • 创建string类型实例
func main() {
    val := reflect.New(reflect.TypeOf(""))
    val.Elem().SetString("hello")
    fmt.Println(val.Elem().String()) // 输出:hello
}

更为复杂的类型的实例,我们继续往下看。

6.2 数组类型

使用reflect在运行时创建一个[3]int类型的数组实例,并设置数组实例各个元素的值:

func main() {
    typ := reflect.ArrayOf(3, reflect.TypeOf(0))
    val := reflect.New(typ)
    arr := val.Elem()
    arr.Index(0).SetInt(1)
    arr.Index(1).SetInt(2)
    arr.Index(2).SetInt(3)
    fmt.Println(arr.Interface()) // 输出:[1 2 3]
    arr1, ok := arr.Interface().([3]int)
    if !ok {
        fmt.Println("not a [3]int")
        return
    }

    fmt.Println(arr1) // [1 2 3]
}

6.3 切片类型

使用reflect在运行时创建一个[]int类型的切片实例,并设置切片实例中各个元素的值:

func main() {
    typ := reflect.SliceOf(reflect.TypeOf(0)) // 切片元素类型
    val := reflect.MakeSlice(typ, 33// 动态创建切片实例
    val.Index(0).SetInt(1)
    val.Index(1).SetInt(2)
    val.Index(2).SetInt(3)
    fmt.Println(val.Interface()) // 输出:[1 2 3]

    sl, ok := val.Interface().([]int)
    if !ok {
        fmt.Println("sl is not a []int")
        return
    }
    fmt.Println(sl) // [1 2 3]
}

6.4 map类型

使用reflect在运行时创建一个map[string]int类型的实例,并设置map实例中键值对:

func main() {
    typ := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
    val := reflect.MakeMap(typ)
    key1 := reflect.ValueOf("one")
    value1 := reflect.ValueOf(1)
    key2 := reflect.ValueOf("two")
    value2 := reflect.ValueOf(2)
    val.SetMapIndex(key1, value1)
    val.SetMapIndex(key2, value2)
    fmt.Println(val.Interface()) // 输出:map[one:1 two:2]

    m, ok := val.Interface().(map[string]int)
    if !ok {
        fmt.Println("m is not a map[string]int")
        return
    }

    fmt.Println(m)
}

6.5 channel类型

使用reflect在运行时创建一个chan int类型的实例,并从该channel实例接收数据:

func main() {
    typ := reflect.ChanOf(reflect.BothDir, reflect.TypeOf(0))
    val := reflect.MakeChan(typ, 0)
    go func() {
        val.Send(reflect.ValueOf(42))
    }()

    ch, ok := val.Interface().(chan int)
    if !ok {
        fmt.Println("ch is not a chan int")
        return
    }
    fmt.Println(<-ch) // 42
}

6.6 结构体类型

使用reflect在运行时创建一个struct类型的实例,并设置该实例的字段值并调用该实例的方法:

type Person struct {
    Name string
    Age  int
}

func (p Person) Greet() {
    fmt.Printf("Hello, my name is %s and I am %d years old\n", p.Name, p.Age)
}

func (p Person) SayHello(name string) {
    fmt.Printf("Hello, %s! My name is %s\n", name, p.Name)
}

func main() {
    typ := reflect.StructOf([]reflect.StructField{
        {
            Name: "Name",
            Type: reflect.TypeOf(""),
        },
        {
            Name: "Age",
            Type: reflect.TypeOf(0),
        },
    })
    ptrVal := reflect.New(typ)
    val := ptrVal.Elem()
    val.FieldByName("Name").SetString("Alice")
    val.FieldByName("Age").SetInt(25)

    person := (*Person)(ptrVal.UnsafePointer())
    person.Greet()         // 输出:Hello, my name is Alice and I am 25 years old
    person.SayHello("Bob"// 输出:Hello, Bob! My name is Alice
}

我们看到:上面代码在反射世界中动态创建了一个带有两个字段Name和Age的struct类型,注意该struct类型与Person并非同一个类型,但他们的内存结构是一致的。这就是上面代码尾部基于反射世界创建出的匿名struct显式转换为Person类型后能正常工作的原因!

注:目前reflect不支持在运行时为动态创建的结构体类型添加新方法。

6.7 指针类型

使用reflect在运行时创建一个指针类型的实例,并通过指针设置其指向内存对象的值:

type Person struct {
    Name string
    Age  int
}

func main() {
    typ := reflect.PtrTo(reflect.TypeOf(Person{}))
    val := reflect.New(typ.Elem())
    val.Elem().FieldByName("Name").SetString("Alice")
    val.Elem().FieldByName("Age").SetInt(25)
    person := val.Interface().(*Person)
    fmt.Println(person.Name) // 输出:Alice
    fmt.Println(person.Age)  // 输出:25
}