概念

计算机科学中,反射是指程序在运行时可以访问、检测和修改它本身状态或行为的一种能力。

Go语言提供了一种机制在运行时更新变量和检查他们的值、调用他们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射。

反射使用场景:

  • 需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。
  • 需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。

反射的缺点:

  • 与反射相关的代码,经常是难以阅读的。
  • Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。
  • 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。

Go反射机制设计的目标之一是任何非反射操作都可以通过反射机制来完成。目前只是大部分的非反射操作可以通过反射完成。

使用反射也可以完成一些使用非反射操作不可能完成的操作。

相关方法

reflect包定义了一个接口和一个结构体,即reflect.Type和reflect.Value。

  • reflect.Type是一个接口,提供了很多方法获取类型相关的信息,和_type关联比较紧密。
  • reflect.Value是一个结构体,结合了_type和data两者,可以获取甚至改变类型的值。

reflect包中提供了两个关于反射的函数来获取上述的接口和结构体

1
2
func TypeOf(i interface{}) Type 
func ValueOf(i interface{}) Value

reflect.TypeOf和reflect.Type

TypeOf用来获取接口中的类型信息。入参是一个空的interface,这样任何类型的对象都可以作为实参传入

调用此函数时,实参会先转化为interface{}类型。这样实参的类型信息、方法集、值信息就都存储到interface变量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.typ)
}
type emptyInterface struct { // 与空接口eface一样
    typ  *rtype
    word unsafe.Pointer
}
func toType(t *rtype) Type {
    if t == nil {
        return nil
    }
    return t // *rtype 也实现了Type
}

reflect.TypeOf函数的参数类型是interface{},函数总是返回一个表示此唯一参数值的动态类型的reflect.Type值。对于接口类型的Type值,需要用间接的方式获取(见例子)。

reflect.Type接口定义的方法

这些方法中,有些适用于所有种类,有些适用于一种或几种类型。

其中有几点需要注意:

  • reflect.Type.NumMethod 方法,对于非接口类型,返回的是类型的所有可导出方法的个数(包括通过内嵌得到的隐式方法),并且reflect.Type.MethodByName 不能用来获取非导出方法;而对于接口类型,没有这样的限制。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
type Type interface {
        // 所有的类型都可以调用下面这些函数

    // 此类型的变量对齐后所占用的字节数
    Align() int
    // 如果是 struct 的字段,对齐后占用的字节数
    FieldAlign() int
    // 返回类型方法集里的第i(传入的参数)个方法
    Method(int) Method
    // 通过名称获取方法
    MethodByName(string) (Method, bool)
    // 获取类型方法集里导出的方法个数
    NumMethod() int
    // 类型名称
    Name() string
    // 返回类型所在的路径,如:encoding/base64
    PkgPath() string
    // 返回类型的大小,和 unsafe.Sizeof 功能类似
    Size() uintptr
    // 返回类型的字符串表示形式
    String() string
    // 返回类型的类型值
    Kind() Kind
    // 类型是否实现了接口 u
    Implements(u Type) bool
    // 是否可以赋值给 u
    AssignableTo(u Type) bool
    // 是否可以类型转换成 u
    ConvertibleTo(u Type) bool
    // 类型是否可以比较
    Comparable() bool

    // 下面这些函数只有特定类型可以调用
    // 如:Key, Elem 两个方法就只能是 Map 类型才能调用

    // 类型所占据的位数
    Bits() int
    // 返回通道的方向,只能是 chan 类型调用
    ChanDir() ChanDir
    // 返回类型是否是可变参数,只能是 func 类型调用
    // 比如 t 是类型 func(x int, y ... float64)
    // 那么 t.IsVariadic() == true
    IsVariadic() bool
    // 返回内部元素的类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用
    Elem() Type
    // 返回结构体类型的第 i 个字段,只能是结构体类型调用
    // 如果 i 超过了总字段数,就会 panic
    Field(i int) StructField
    // 返回嵌套的结构体的字段
    FieldByIndex(index []int) StructField
    // 通过字段名称获取字段
    FieldByName(name string) (StructField, bool)
    // FieldByNameFunc returns the struct field with a name
    // 返回名称符合 func 函数的字段
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    // 获取函数类型的第 i 个参数的类型
    In(i int) Type
    // 返回 map 的 key 类型,只能由类型 map 调用
    Key() Type
    // 返回 Array 的长度,只能由类型 Array 调用
    Len() int
    // 返回类型字段的数量,只能由类型 Struct 调用
    NumField() int
    // 返回函数类型的输入参数个数
    NumIn() int
    // 返回函数类型的返回值个数
    NumOut() int
    // 返回函数类型的第 i 个值的类型
    Out(i int) Type
        // 返回类型结构体的相同部分
    common() *rtype
    // 返回类型结构体的不同部分
    uncommon() *uncommonType
}

例子

间接方式获取接口类型的Type值

如果调用reflect.TypeOf时直接传入这个接口对象,由于它的动态值是nil,得到的Type也是nil。

需要用下面的方法间接获取:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
    "fmt"
    "reflect"
)

type si interface {
}

func main() {
    var i si
    t := reflect.TypeOf(i) // 没有动态类型时,Type为nil。拿不到实际的接口类型
    fmt.Println(t) // nil

    t = reflect.TypeOf(&i) // 使用指针的方式,就可以间接得到接口类型
    fmt.Println(t.Elem()) // main.si
}

t的动态类型是Ptr,可以使用Elem()方法获取内部元素类型,即我们想要的接口类型。

除了用指针的方式间接实现,还可以用切片的方式。

1
2
3
4
5
6
func main() {
    var i []si
    t := reflect.TypeOf(i)
    fmt.Println(t) // []main.si
    fmt.Println(t.Elem()) // main.si
}

获取类型信息及可比较性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func main() {
    type A = [16]int16
    var c <-chan map[A][]byte

    tc := reflect.TypeOf(c)
    fmt.Println(tc.Kind())    // chan
    fmt.Println(tc.ChanDir()) // <-chan

    tm := tc.Elem()
    ta, tb := tm.Key(), tm.Elem()
    fmt.Println(tm.Kind(), ta.Kind(), tb.Kind()) // map array slice

    tx, ty := ta.Elem(), tb.Elem()
    // byte 是 uint8的类型别名
    fmt.Println(tx.Kind(), ty.Kind()) // int16 uint8

    // 切片和map不可比较,chan可比较
    fmt.Println(tb.Comparable(), tm.Comparable()) // false false
    fmt.Println(tc.Comparable())                  // true
}

结构体字段方法和接口方法的可见性

  • 对于结构体,反射只能读到导出方法;对于接口,反射能读到全部方法,包括非导出的
  • 对于结构体,反射可以读到非导出字段,但是不能修改。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
    "fmt"
    "reflect"
)

type F func(string, int) bool

// 给函数类型配一个方法,在方法中调用自己
func (f F) m(s string) bool {
    return f(s, 32)
}
func (f F) Mm() {}

type interf interface {
    m(s string) bool
    Mm()
}

func main() {
    var x struct {
        f F
        i interf
    }

    tx := reflect.TypeOf(x)
    fmt.Println(tx.Kind())        // struct
    fmt.Println(tx.NumField())    // 2
    fmt.Println(tx.Field(1).Name) // i

    tf, ti := tx.Field(0).Type, tx.Field(1).Type    // 可以读到结构体非导出字段
    fmt.Println(tf.Kind(), ti.Kind())    // func, interface
    fmt.Println(tf.IsVariadic())         // func是否是可变参数,false
    fmt.Println(tf.NumIn(), tf.NumOut()) // 2, 1

    fmt.Println(tf.NumMethod(), ti.NumMethod()) // 1 2 结构体和接口非导出方法的可见性有区别
    _, ok1 := tf.MethodByName("m")              // 结构体读不到非导出方法
    _, ok2 := ti.MethodByName("m")              // 接口可以读到非导出方法
    fmt.Println(ok1, ok2)                       // false, true
}

获取结构体字段标签信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
    "fmt"
    "reflect"
)

type T struct {
    X    int  `max:"99" min:"0" default:"0"`
    Y, Z bool `optional:"yes,ok"`
}

func main() {
    t := reflect.TypeOf(T{})
    x := t.Field(0).Tag
    y := t.Field(1).Tag
    z := t.Field(2).Tag

    fmt.Println(reflect.TypeOf(x)) // reflect.StructTag
    v, present := x.Lookup("max")  // 返回string, bool
    fmt.Println(v, present)        // 99 true
    fmt.Println(x.Get("max"))      // 99

    fmt.Println(x.Lookup("optional")) // false
    fmt.Println(y.Lookup("optional")) // yes,ok true
    fmt.Println(z.Lookup("optional")) // yes,ok true
}

注意,tag中键值对中间不能包含空格。

动态创建组合类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
    "fmt"
    "reflect"
)

func main() {
    ta := reflect.ArrayOf(5, reflect.TypeOf(1)) // ArrayOf(length int, elem Type) Type
    fmt.Println(ta)                             // [5]int

    tc := reflect.ChanOf(reflect.SendDir, ta) // ChanOf(dir ChanDir, t Type) Type {
    fmt.Println(tc)                           // chan<- [5]int

    tp := reflect.PtrTo(ta)
    fmt.Println(tp) // *[5]int

    tm := reflect.MapOf(ta, tc) // MapOf(key, elem Type) Type
    fmt.Println(tm)             // map[[5]int]chan<-[5]int

    // FuncOf(in, out []Type, variadic bool) Type
    tf := reflect.FuncOf([]reflect.Type{ta}, []reflect.Type{tp, tc}, false)
    fmt.Println(tf) // func([5]int) (*[5]int, chan<-[5]int)

    tt := reflect.StructOf([]reflect.StructField{
        {Name: "Name", Type: reflect.TypeOf("Jack")},
    })
    fmt.Println(tt)            // struct { Name string }
    fmt.Println(tt.NumField()) // 1
}

截止到Go1.17,无法用反射动态创建一个接口类型。

reflect.ValueOf和reflect.Value

ValueOf函数的返回值Value表示interface{}里实际存储的变量,它能提供实际变量与类型和值各种信息,还能对可修改的值进行修改。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }
        // ……
    return unpackEface(i)
}

// 分解 eface
func unpackEface(i interface{}) Value {
    e := (*emptyInterface)(unsafe.Pointer(&i))

    t := e.typ
    if t == nil {
        return Value{}
    }

    f := flag(t.Kind())
    if ifaceIndir(t) {
        f |= flagIndir
    }
    return Value{t, e.word, f}
}

源码中先将i转换成 *emptyInterface 类型, 再将它的 typ字段和 word字段以及一个标志位字段组装成一个 Value结构体,这就是 ValueOf 函数的返回值,它包含类型结构体指针、真实数据的地址、标志位。

和reflect.TypeOf函数类似,reflect.ValueOf函数也只有一个interface{}参数。当我们将一个接口值传递给一个 reflect.ValueOf 函数调 用时,此调用返回的是代表着此接口值的动态值的一个 reflect.Value 值。 因此,也需要通过间接的途径 获得一个代表一个接口值的 reflect.Value 值(见例子)。

reflect.Value结构体提供的方法

Value结构体定义了很多方法,通过这些方法可以直接操作Value字段ptr所指向的实际数据。

这些方法有的适用于所有类型的值,有的只适用于一种或几种类型的值。

有几点需要注意:

  • 一个 reflect.Value 值的 CanSet 方法将返回此 reflect.Value 值代表的Go值是否可以被修改(可以 被赋值)。 如果一个Go值可以被修改,则我们可以调用对应的 reflect.Value 值的 Set 方法来修改此 Go值。
  • reflect.ValueOf()函数直接返回的Value值都是不可修改的。通常需要.Elem()再获取一下内部元素值。因为因为Go的参数传递是值传递,所以通常传给ValueOf()方法的是一个指针类型。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 // 设置切片的 len 字段,如果类型不是切片,就会panic
 func (v Value) SetLen(n int)
 // 设置切片的 cap 字段
 func (v Value) SetCap(n int)
 // 设置字典的 kv
 func (v Value) SetMapIndex(key, val Value)
 // 返回切片、字符串、数组的索引 i 处的值
 func (v Value) Index(i int) Value
 // 根据名称获取结构体的内部字段值
 func (v Value) FieldByName(name string) Value
// 用来获取 int 类型的值
func (v Value) Int() int64
// 用来获取结构体字段(成员)数量
func (v Value) NumField() int
// 尝试向通道发送数据(不会阻塞)
func (v Value) TrySend(x reflect.Value) bool
// 通过参数列表 in 调用 v 值所代表的函数(或方法
func (v Value) Call(in []Value) (r []Value) 
// 调用变参长度可变的函数
func (v Value) CallSlice(in []Value) []Value 
// 返回v对应的值,v必须是Interface或者Ptr类型
func (v Value) Elem() Value
// 返回v作为Ptr指向的Value;如果v不是Ptr,返回v
func (v Value) Indirect(v Value) Value
// It returns false if v is the zero Value
func (v Value) IsValid() bool

有两种方法获取一个代表着一个指针所引用着的值的 reflect.Value 值:

  1. 通过调用代表着此指针值的 reflect.Value 值的 Elem 方法。
  2. 将代表着此指针值的 reflect.Value 值的传递给一个 reflect.Indirect 函数调用,内部也是调了Elem()。 (如果传 递给一个 reflect.Indirect 函数调用的实参不代表着一个指针值,则此调用返回此实参的一个 复制。)

例子

如何间接获取接口类型值的Value值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
    "fmt"
    "reflect"
)

func main() {
    var z = 123
    var y = &z
    var x interface{} = y

    v := reflect.ValueOf(&x)
    vx := v.Elem()  // 获取Ptr指向的值,即接口类型对象x的值
    vy := vx.Elem() // 获取interface包含的值
    vz := vy.Elem() // 获取Ptr指向的值

    vz.Set(reflect.ValueOf(789))
    fmt.Println(z) // 789
}

利用反射修改值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
    "fmt"
    "reflect"
)

func main() {
    n := 123
    p := &n
    vp := reflect.ValueOf(p)

    fmt.Println(vp.CanSet(), vp.CanAddr()) // false false
    vn := vp.Elem()
    fmt.Println(vn.CanSet(), vn.CanAddr()) // true true
    vn.Set(reflect.ValueOf(789))
    fmt.Println(n) // 789
}

结构体的非导出值不可以反射修改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
    "fmt"
    "reflect"
)

func main() {
    var s struct {
        X interface{}
        y interface{}
    }

    vp := reflect.ValueOf(&s)
    vs := reflect.Indirect(vp) // 如vp代表指针,则执行vp.Elem()
    vx, vy := vs.Field(0), vs.Field(1)     // interface{}对应的Value,都是nil
    fmt.Println(vx.CanSet(), vx.CanAddr()) // true true
    fmt.Println(vy.CanSet(), vy.CanAddr()) // false true 可以读到结构体非导出字段,但是不能修改

    vb := reflect.ValueOf(123)
    vx.Set(vb)
    // vy.Set(vb) // will panic

    fmt.Println(s) // {123 nil}
}

上例也显示了如何间接获取底层值为接口值的reflect.Value值。

自定义泛型函数绑定到不同类型的函数值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
    "fmt"
    "reflect"
)

func InvertSlice(args []reflect.Value) []reflect.Value {
    inSlice, n := args[0], args[0].Len()
    outSlice := reflect.MakeSlice(inSlice.Type(), 0, n)
    for i := n - 1; i >= 0; i-- {
        element := inSlice.Index(i)
        outSlice = reflect.Append(outSlice, element)
    }
    return []reflect.Value{outSlice}
}

func Bind(p interface{}, f func([]reflect.Value) []reflect.Value) {
    invert := reflect.ValueOf(p).Elem()
    invert.Set(reflect.MakeFunc(invert.Type(), f))
}

func main() {
    var invertInts func([]int) []int
    Bind(&invertInts, InvertSlice)
    fmt.Println(invertInts([]int{1, 2, 3}))

    var invertStrs func([]string) []string
    Bind(&invertStrs, InvertSlice)
    fmt.Println(invertStrs([]string{"a", "b", "c"}))
}

利用反射调用函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
    "fmt"
    "reflect"
)

type T struct {
    A, b int
}

func (t T) Add(n int) (int, int) {
    return t.A + n, t.b + n
}

func main() {
    t := T{1, 2}
    vt := reflect.ValueOf(t)
    vm := vt.MethodByName("Add")
    res := vm.Call([]reflect.Value{reflect.ValueOf(1)})
    fmt.Println(res[0].Int(), res[1].Int()) // 2 3

    neg := func(x int) int {
        return -x
    }
    vf := reflect.ValueOf(neg)
    fmt.Println(vf.Call([]reflect.Value{reflect.ValueOf(1)})[0].Int()) // -1
    fmt.Println(vt.FieldByName("b").Int())                             // 2

    fmt.Println(vf.Call([]reflect.Value{
        vt.FieldByName("A"),
        // 如果是b,会panic: reflect: reflect.Value.call using value obtained using unexported field
    })[0].Int()) // -1
}

注意:结构体的非导出字段值不能用作反射函数调用中的实参,即Call里面的参数。在Call外面,是可以获取非导出字段值的。

反射操作map

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
    "fmt"
    "reflect"
)

func main() {
    valueOf := reflect.ValueOf

    m := map[string]int{"Unix": 1973, "Windows": 1985}
    v := valueOf(m)
    // 第二个实参的Value为零值时,表示删除一个条目
    v.SetMapIndex(valueOf("Windows"), reflect.Value{})
    v.SetMapIndex(valueOf("Unix"), valueOf(1991))

    for i := v.MapRange(); i.Next(); {
        fmt.Println(i.Key(), "\t:", i.Value())
    }
    // unix: 1991
}

反射操作channel

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
    "fmt"
    "reflect"
)

func main() {
    c := make(chan string, 2)
    vc := reflect.ValueOf(c)
    vc.Send(reflect.ValueOf("C"))

    succeeded := vc.TrySend(reflect.ValueOf("C++"))
    fmt.Println(succeeded) // true
    succeeded = vc.TrySend(reflect.ValueOf("GO"))
    fmt.Println(succeeded)          // false
    fmt.Println(vc.Len(), vc.Cap()) // 2 2
    vs, succeeded := vc.TryRecv()
    fmt.Println(vs.String(), succeeded) // C true
    vs, sentBeforeClosed := vc.Recv()
    fmt.Println(vs.String(), sentBeforeClosed) // C++ true

    vs, succeeded = vc.TryRecv()
    fmt.Println(vs.String(), succeeded) // <invalid Value> false
}

reflect.Value 类型的 TrySend 和 TryRecv 方法对应着只有一个 case 分支和一个 default 分支的select 流程控制代码块。

反射模拟不定数量case的select

可以使用 reflect.Select 函数在运行时刻来模拟具有不定 case 分支数量的 select 流程控制代码 块。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
    "fmt"
    "reflect"
)

func main() {
    c := make(chan int, 1)
    vc := reflect.ValueOf(c)

    succeeded := vc.TrySend(reflect.ValueOf(123))
    fmt.Println(succeeded, vc.Len(), vc.Cap()) // true 1 1

    vSend, vZero := reflect.ValueOf(789), reflect.Value{}
    branches := []reflect.SelectCase{
        {Dir: reflect.SelectDefault, Chan: vZero, Send: vZero},
        {Dir: reflect.SelectRecv, Chan: vc},
        {Dir: reflect.SelectSend, Chan: vc, Send: vSend},
    }
    selIndex, vRecv, sentBeforeClosed := reflect.Select(branches) // 选择一个可以执行的case执行
    fmt.Println(selIndex)                                         // 1 选择的索引
    fmt.Println(sentBeforeClosed)                                 // true
    fmt.Println(vRecv.Int())                                      // 123

    vc.Close()
    // channel已关闭,发送会panic,所以删除最后一个case
    selIndex, _, sentBeforeClosed = reflect.Select(branches[:2])
    fmt.Println(selIndex, sentBeforeClosed) // 1 false 从已关闭的channel中取到了零值
}

切片到数组的转换

从Go 1.17开始,一个切片可以被转化为一个相同元素类型的数组的指针类型。

1
2
3
4
5
var x []int
var y = make([]int, 0)
var x0 = (*[0]int)(x) // okay, x0 == nil
var y0 = (*[0]int)(y) // okay, y0 != nil
 _, _ = x0, y0

但是如果在 这样的一个转换中数组类型的长度过长,将导致恐慌产生。 因此Go 1.17同时引入了一 个 Value.CanConvert(T Type) 方法,用来检查一个转换是否会成功。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
    "fmt"
    "reflect"
)

func main() {
    s := reflect.ValueOf([]int{1, 2, 3, 4, 5})
    t1 := reflect.TypeOf(&[5]int{})
    t2 := reflect.TypeOf(&[6]int{})

    fmt.Println(s.CanConvert(t1)) // true
    fmt.Println(s.CanConvert(t2)) // false 长度不匹配

    ts := s.Type()
    fmt.Println(ts.ConvertibleTo(t1)) // true
    fmt.Println(ts.ConvertibleTo(t2)) // true 类型上是支持的
}

Value.Type()和Value.Interface()方法

通过这两个方法,可以将Value转换为Type和interface。Type()方法可以返回变量的类型,Interface()方法可以还原为原来的interface.

三者的转换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
    "fmt"
    "reflect"
    "time"
)

func main() {
    vx := reflect.ValueOf(123)
    vy := reflect.ValueOf("abc")
    vz := reflect.ValueOf([]bool{false, true})
    vt := reflect.ValueOf(time.Time{})

    x := vx.Interface().(int)
    y := vy.Interface().(string)
    z := vz.Interface().([]bool)
    m := vt.MethodByName("IsZero").Interface().(func() bool)
    fmt.Println(x, y, z, m()) // 123 abc [false true] true

    type T struct{ x int }
    t := &T{3}
    v := reflect.ValueOf(t).Elem().Field(0)
    fmt.Println(v) // 3
    // 调用一个代表着非导出字段的 reflect.Value 值的 Interface 方法会导致panic
    //fmt.Println(v.Interface()) // panic: cannot return value obtained from unexported field or method
}

反射修改变量的限制

利用反射机制,对于结构体中未导出的成员,可以读取,但是不能修改。

Type和Value的一些同名方法的区别

Type接口的方法和Value结构体的方法,有些同名,但是处理结果可能不一样。

NumIn()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
    "fmt"
    "reflect"
)

type A struct {
}

func (a A) String() {
}

func main() {
    a := A{}
    m, _ := reflect.TypeOf(a).MethodByName("String")
    fmt.Println(m.Type.NumIn()) // 1 包含receiver

    mv := reflect.ValueOf(a).MethodByName("String")
    fmt.Println(mv.Type().NumIn()) // 0 不包含receiver
}

反射三定律

  1. Reflection goes from interface value to reflection object. 即 interface{}变量能够转为反射对象
  2. Reflection goes from reflection object to interface value. 即 反射对象能转为interface
  3. To modify a reflection object, the value must be settable. 即 如果想要更新一个reflect.Value,那么它持有的值必须是可被更新的。

第一条是最基本的,反射是一种检测存储在interface{}中的类型和值的机制。可以通过TypeOf()和ValueOf()得到对应Type和Value

第二条和第一条相反,可以将Value通过Interface()方法还原回interface变量

第三条是说:如果需要操作一个反射变量,那么它必须是可设置的。反射变量可设置的本质是它存储了原变量本身,这样对反射变量的操作,就会反映到原变量本身;反之,如果反射变量不能代表原变量,那么操作了反射变量,不会对原变量产生任何影响,这会给使用者带来疑惑。所以后面这种情况在语言层面是不被允许的。

1
2
3
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.

上面代码,调用reflect.ValueOf时传递的其实是x的拷贝,v代表的不是x本身,所以对v操作是禁止的。

1
2
3
4
var x float64 = 3.4
p := reflect.ValueOf(&x)
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())

上面会输出

1
2
type of p: *float64
settability of p: false

可以看到,p还不是代表x,而是一个Ptr。因此需要用p.Elem()获得对应的x。

1
2
3
4
v := p.Elem()
v.SetFloat(7.1)
fmt.Println(v.Interface()) // 7.1
fmt.Println(x) // 7.1

因此,在反射中修改变量,必须能拿到原变量的地址。

例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package main

import (
    "reflect"
)

type Child struct {
    Name     string
    Grade    int
    Handsome bool
}

type Adult struct {
    ID         string `qson:"Name"`
    Occupation string
    Handsome   bool
}

// 如果输入参数 i 是 Slice,元素是结构体,有一个字段名为 `Handsome`,
// 并且有一个字段的 tag 或者字段名是 `Name` ,
// 如果该 `Name` 字段的值是 `qcrao`,
// 就把结构体中名为 `Handsome` 的字段值设置为 true。
func handsome(i interface{}) {
    v := reflect.ValueOf(i)
    if v.Kind() != reflect.Slice {
        return
    }
    if e := v.Type().Elem(); e.Kind() != reflect.Struct {
        return
    }

    // 确定结构体的字段名含有 "ID" 或者 json tag 标签为 `name`
    // 确定结构体的字段名 "Handsome"
    st := v.Type().Elem()

    // 寻找字段名为 Name 或者 tag 的值为 Name 的字段
    foundName := false
    for i := 0; i < st.NumField(); i++ {
        f := st.Field(i)
        tag := f.Tag.Get("qson")

        if (tag == "Name" || f.Name == "Name") && f.Type.Kind() == reflect.String {
            foundName = true
            break
        }
    }
    if !foundName {
        return
    }

    niceField, foundHandsome := st.FieldByName("Handsome")
    if foundHandsome == false || niceField.Type.Kind() != reflect.Bool {
        return
    }

    // 设置名字为 "qcrao" 的对象的 "Handsome" 字段为 true
    for i := 0; i < v.Len(); i++ {
        e := v.Index(i)
        handsome := e.FieldByName("Handsome")

        // 寻找字段名为 Name 或者 tag 的值为 Name 的字段
        var name reflect.Value
        for j := 0; j < st.NumField(); j++ {
            f := st.Field(j)
            tag := f.Tag.Get("qson")

            if tag == "Name" || f.Name == "Name" {
                name = v.Index(i).Field(j)
            }
        }

        if name.String() == "qcrao" {
            handsome.SetBool(true)
        }
    }
}

func main() {
    children := []Child{
        {Name: "Ava", Grade: 3, Handsome: true},
        {Name: "qcrao", Grade: 6, Handsome: false},
    }

    adults := []Adult{
        {ID: "Steve", Occupation: "Clerk", Handsome: true},
        {ID: "qcrao", Occupation: "Go Programmer", Handsome: false},
    }

    handsome(adults)
    handsome(children)
}

应用

Json序列化

Go语言中提供了两个函数用于序列化和反序列化

1
2
func Marshal(v interface{}) ([]byte, error)
func Unmarshal(data []byte, v interface{}) error

两个函数的参数都包含interface,具体实现的时候,都会用到反射相关的特性。

对于序列化和反序列化函数,均需要知道参数的所有字段,包括字段类型和值,再调用相关的 get 函数或者 set 函数进行实际的操作。

DeepEqual

在测试函数中,经常会需要这样的函数:判断两个变量的实际内容完全一致。

例如:如何判断两个 slice 所有的元素完全相同;如何判断两个 map 的 key 和 value 完全相同等等。

上述问题,可以通过 DeepEqual 函数实现。

1
func DeepEqual(x, y interface{}) bool

DeepEqual 函数的参数是两个 interface,也就是可以输入任意类型,输出 true 或者 flase 表示输入的两个变量是否“深度”相等。

DeepEqual的定义

先明白一点,如果是不同的类型,即使是底层类型相同,相应的值也相同,那么两者也不是“深度”相等。

1
2
3
4
5
6
7
8
9
type MyInt int
type YourInt int

func main() {
	m := MyInt(1)
	y := YourInt(1)

	fmt.Println(reflect.DeepEqual(m, y)) // false
}

在源码里,有对 DeepEqual 函数的非常清楚地注释,列举了不同类型下DeepEqual 的比较情形,这里做一个总结

类型 深度相等情形
Array 相同索引处的元素“深度”相等
Struct 相应字段,包含导出和不导出,“深度”相等
Func 只有两者都是 nil 时
Interface 两者存储的具体值“深度”相等
Map 1、都为 nil;2、非空、长度相等,指向同一个 map 实体对象,或者相应的 key 指向的 value “深度”相等
Pointer 1、使用 == 比较的结果相等;2、指向的实体“深度”相等
Slice 1、都为 nil;2、非空、长度相等,首元素指向同一个底层数组的相同元素,即 &x[0] == &y[0] 或者 相同索引处的元素“深度”相等
numbers, bools, strings, and channels 使用 == 比较的结果为真

一般情况下,DeepEqual 的实现只需要递归地调用 == 就可以比较两个变量是否是真的“深度”相等。

但是,有一些异常情况:

  • func 类型是不可比较的类型,只有在两个 func 类型都是 nil 的情况下,才是“深度”相等;
  • float 类型,由于精度的原因,也是不能使用 == 比较的;
  • 包含 func 类型或者 float 类型的 struct, interface, array 等。
  • 对于指针而言,当两个值相等的指针就是“深度”相等,因为两者指向的内容是相等的,即使两者指向的是 func 类型或者 float 类型,这种情况下不关心指针所指向的内容。
  • 同样对于指向相同 slice, map 的两个变量也是“深度”相等的,不关心 slice, map 具体的内容。
  • 对于“有环”的类型,比如循环链表,比较两者是否“深度”相等的过程中,需要对已比较的内容作一个标记,一旦发现两个指针之前比较过,立即停止比较,并判定二者是深度相等的。这样做的原因是,及时停止比较,避免陷入无限循环。

DeepEqual源码分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func DeepEqual(x, y interface{}) bool {
	if x == nil || y == nil {
		return x == y
	}
	v1 := ValueOf(x)
	v2 := ValueOf(y)
	if v1.Type() != v2.Type() {
		return false
	}
	return deepValueEqual(v1, v2, make(map[visit]bool), 0)
}
  • 首先查看两者是否有一个是 nil 的情况,这种情况下,只有两者都是 nil,函数才会返回 true。
  • 接着使用反射,获取x,y 的反射对象,并且立即比较两者的类型。这里实际上是动态类型,如果类型不同,直接返回 false。
  • 最后最核心的内容在子函数 deepValueEqual 中。

代码比较长,思路却比较简单清晰:核心是一个 switch 语句,识别输入参数的不同类型,分别递归调用 deepValueEqual 函数,一直递归到最基本的数据类型,比较 int,string 等可以直接得出 true 或者 false,再一层层地返回,最终得到“深度”相等的比较结果。

以map类型的比较为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// deepValueEqual 函数
// ……

case Map:
	if v1.IsNil() != v2.IsNil() {
		return false
	}
	if v1.Len() != v2.Len() {
		return false
	}
	if v1.Pointer() == v2.Pointer() {
		return true
	}
	for _, k := range v1.MapKeys() {
		val1 := v1.MapIndex(k)
		val2 := v2.MapIndex(k)
		if !val1.IsValid() || !val2.IsValid() ||
                    !deepValueEqual(v1.MapIndex(k), v2.MapIndex(k), visited, depth+1) {
			return false
		}
	}
	return true
	
// ……	

其中visited是一个 map,用于记录递归过程中,已经比较过的“对”。

1
2
3
4
5
6
7
type visit struct {
	a1  unsafe.Pointer
	a2  unsafe.Pointer
	typ Type
}

var visited map[visit]bool

比较过程中,一旦发现比较的“对”,已经在 map 里出现过的话,直接从map里获取结果。

Sql语句生成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package main

import (
    "fmt"
    "reflect"
    "strings"
)

/*
- reflect.Type和 reflect.Value
- reflect.Kind
- NumField() 和 Field() 方法
- Int() 和 String()

最终的输出是这三个
insert into order values(456, 56)
insert into employee values("Naveen", 565, "Coimbatore", 90000, "India")
unsupported type
*/

type order struct {
    ordId      int
    customerId int
}

type employee struct {
    name    string
    id      int
    address string
    salary  int
    country string
}

func (employee) TableName() string {
    return "employee"
}

func main() {
    o := order{
        ordId:      456,
        customerId: 56,
    }
    createQuery(o)

    e := employee{
        name:    "Naveen",
        id:      565,
        address: "Coimbatore",
        salary:  90000,
        country: "India",
    }
    createQuery(e)
    i := 90
    createQuery(i)
}

func createQuery(q interface{}) {
    t := reflect.TypeOf(q)
    if t.Kind() != reflect.Struct {
        fmt.Println("unsupported type")
        return
    }

    v := reflect.ValueOf(q)
    tableName := t.Name()
    tableNameFunc := v.MethodByName("TableName")
    if tableNameFunc.IsValid() {
        tableName = tableNameFunc.Call(nil)[0].String()
    }

    var values strings.Builder
    for i := 0; i < t.NumField(); i++ {
        iv := v.Field(i)
        switch iv.Kind() {
        case reflect.String:
            _, _ = fmt.Fprintf(&values, "\"%s\", ", iv.String())
        case reflect.Int:
            _, _ = fmt.Fprintf(&values, "%d, ", iv.Int())
        }
    }

    sql := fmt.Sprintf("insert into %s values (%s)",
        tableName,
        strings.TrimRight(values.String(), ", "))
    fmt.Println(sql)
}

参考链接

码农桃花源

go101