Skip to content

反射

介绍

反射是应用程序动态检查其所拥有的结构,尤其是类型对象的一种能力。每种语言的反射模型都不同,有些语言根本不支持反射。
Go 语言实现了反射,反射机制就是在运行时动态调用对象的方法和属性,标准库的 reflect 包提供了相关的功能。
在 reflect 包中,通过 reflect.TypeOf() 和 reflect.ValueOf() 分别从类型、值的角度来描述一个 Go 对象

1
2
3
4
5
func TypeOf(i interface{}) Type
type Type interface

func ValueOf(i interface{}) Value
type Value struct

在 Go 语言的实现中,一个接口类型的变量存储了两个信息,即一个 <值, 类型><value,type>

value 是实际变量值,type 是实际变量的类型,不能是接口类型。
两个简单的函数 reflect.TypeOf() 和 reflect.ValueOf(),返回被检查对象的类型和值

例如,x 被定义为: var x float64 = 3.4,那么 reflect.TypeOf(x) 返回 float64,reflect.ValueOf(x) 返回 3.4。
实际上,反射通过检查一个接口类型变量从而得到该变量的类型成值,变量可以是任意类型(空接口)。这点从下面两个函数签名能够很明显地看出来

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

在 Go 语言中实现反射,是将 "接口类型变量" 转换为 "反射类型对象"。
上面函数中 i 就是接口类型变量,而返回值就是 reflect.Type 和 reflect.Value。
在实际应用中,i 一般是一个具体类型的变量,因为 i 对应一个空接口类型;而反射正好是将接口类型转为反射类型,得到反射类型后就可以做更多的事情了

所以,当调用 reflect.TypeOf(x) 时,x 被当作一个接口变量处理,然后 reflect.TypeOf() 对接口变量进行拆解,返回其反射类型信息

reflect.Type 和 reflect.Value 还有许多其他检查和操作方法

Type 的主要方法有:

  • Kind(): 返回一个常量,表示具体类型的底层类型
  • Elem(): 返回指针、数组、切片、字典、通道等类型。这个方法要慎用,如果用于其他类型会出现运行时异常

Value 的主要方法有:

  • Kind(): 返回一个常量,表示具体类型的底层类型
  • Type(): 返回具体类型所对应的 reflect.Type(静态类型)

反射让我们可以在程序运行时动态检测类型和变量,例如它的值、方法和类型,甚至可以在运行时修改和创建变量、函数和结构。这对于没有源代码的包尤其有用

反射是一个强大的工具,但对性能有一定的影响,因此除非有必要,应当避免使用或小心使用。
下面代码针对 int、自定义类型、数组以及结构体分别使用反射机制,其中的差异请看代码注释

 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
package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    name string
}

type MyInt int

func main() {
    var a int = 9
    v := reflect.ValueOf(a)                         // 返回 Value 类型对象,值为 9
    t := reflect.TypeOf(a)                          // 返回 Type 类型对象,值为 int
    fmt.Println(v, t, v.Type(), v.Kind(), t.Kind()) // Kind() 返回底层基础类型

    var mi MyInt = 99
    mv := reflect.ValueOf(mi)                            // 返回 Value 类型对象,值为 99
    mt := reflect.TypeOf(mi)                             // 返回 Type 类型对象,值为 MyInt
    fmt.Println(mv, mt, mv.Type(), mv.Kind(), mt.Kind()) // Kind() 返回底层基础类型

    var b [5]int = [5]int{5, 6, 7, 8}
    fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(), reflect.TypeOf(b).Elem()) // [5]int array int

    var Pupil Student
    p := reflect.ValueOf(Pupil) // 使用 ValueOf 获得结构体的 Value 对象

    fmt.Println(p.Type()) // 输出: Student
    fmt.Println(p.Kind()) // 输出: struct
}

输出:

1
2
3
4
5
9 int int int int
99 main.MyInt main.MyInt int int
[5]int array int
main.Student
struct

在 Go 语言中,静态类型就是变量声明时赋予的类型,也就是在反射中的 reflect.Type 对应的值,而 kind() 对应的是基础类型

Kind() 大概会返回切片、字典、指针、结构体、接口、字符串、数组、函数、整型或其他基础类型。
如上面代码中 Kind() 返回结构体: fmt.Println(p.Kind()),而 Type() 返回静态类型名 Student: fmt.Println(p.Type())
MyInt 是静态类型,而 int 是它的基础类型

Type() 返回的是静态类型,而 kind() 返回的是基础类型

类型

反射(reflect)让我们能在运行期探知对象的类型信息和内存结构,这从一定程度上弥补了静态语言在动态行为上的不足。
同时,反射还是实现元编程的重要手段

和 C 数据结构一样,Go 对象头部并没有类型指针,通过其自身是无法在运行期获知任何类型相关信息的。
反射操作所需的全部信息都源自接口变量。
接口变量除存储自身类型外,还会保存实际对象的类型数据

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

这两个反射入口函数,会将任何传入的对象转换为接口类型。

在面对类型时,需要区分 Type 和 Kind。
前者表示真实类型(静态类型),后者表示其基础结构(底层类型)类别

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

import (
    "fmt"
    "reflect"
)

type X int

func main() {
    var a X = 100
    t := reflect.TypeOf(a)

    fmt.Println(t.Name(), t.Kind())
}

输出:

1
X int

所以在类型判断上,须选择正确的方式

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

import (
    "fmt"
    "reflect"
)

type X int
type Y int

func main() {
    var a, b X = 100, 200
    var c Y = 300

    ta, tb, tc := reflect.TypeOf(a), reflect.TypeOf(b), reflect.TypeOf(c)

    fmt.Println(ta == tb, ta == tc)
    fmt.Println(ta.Kind() == tc.Kind())
}

输出:

1
2
true false
true

除通过实际对象获取类型外,也可直接构造一些基础复合类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := reflect.ArrayOf(10, reflect.TypeOf(byte(0)))
    m := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))

    fmt.Println(a, m)
}

输出:

1
[10]uint8 map[string]int

传入对象应区分基类型和指针类型,因为它们并不属于同一类型。

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

import (
    "fmt"
    "reflect"
)

func main() {
    x := 100

    tx, tp := reflect.TypeOf(x), reflect.TypeOf(&x)

    fmt.Println(tx, tp, tx == tp)
    fmt.Println(tx.Kind(), tp.Kind())
    fmt.Println(tx == tp.Elem())
}

输出:

1
2
3
int *int false
int ptr
true

方法 Elem 返回指针、数组、切片、字典(值)或通道的基类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import (
    "fmt"
    "reflect"
)

func main() {
    fmt.Println(reflect.TypeOf(map[string]int{}).Elem())
    fmt.Println(reflect.TypeOf([]int32{}).Elem())
}

输出:

1
2
int
int32

只有在获取结构体指针的基类型后,才能遍历它的字段

 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
package main

import (
    "fmt"
    "reflect"
)

type user struct {
    name string
    age  int
}

type manager struct {
    user
    title string
}

func main() {
    var m manager
    t := reflect.TypeOf(&m)

    if t.Kind() == reflect.Ptr { // 获取指针的基类型
        t = t.Elem()
    }

    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        fmt.Println(f.Name, f.Type, f.Offset)

        if f.Anonymous { // 输出匿名字段结构
            for x := 0; x < f.Type.NumField(); x++ {
                af := f.Type.Field(x)
                fmt.Println(af.Name, af.Type)
            }
        }
    }
}

输出:

1
2
3
4
user main.user 0
name string
age int
title string 24

对于匿名字段,可用于多级索引(按定义顺序)直接访问

 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
package main

import (
    "fmt"
    "reflect"
)

type user struct {
    name string
    age  int
}

type manager struct {
    user
    title string
}

func main() {
    var m manager
    t := reflect.TypeOf(m)

    name, _ := t.FieldByName("name") // 按名称查找
    fmt.Println(name.Name, name.Type)

    age := t.FieldByIndex([]int{0, 1}) // 按多级索引查找
    fmt.Println(age.Name, age.Type)
}

输出:

1
2
name string
age int

FieldByName 不支持多级名称,如有同名遮蔽,须通过匿名字段二次获取

和 Type 获取类型信息不同,Value 专注于对象实例数据读写

接口变量会复制对象,且是 unaddressable 的,所以要想修改目标对象,就必须使用指针

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

import (
    "fmt"
    "reflect"
)

func main() {
    a := 100
    va, vp := reflect.ValueOf(a), reflect.ValueOf(&a).Elem()

    fmt.Println(va.CanAddr(), va.CanSet())
    fmt.Println(vp.CanAddr(), vp.CanSet())
}

输出:

1
2
false false
true true

就算传入指针,一样需要通过 Elem 获取目标对象。因为被接口存储的指针本身是不能寻址和进行设置操作的

注意,不能对非导出字段直接进行设置操作,无论是当前包还是外包

 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
package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

type User struct {
    Name string
    code int
}

func main() {
    p := new(User)
    v := reflect.ValueOf(p).Elem()

    name := v.FieldByName("Name")
    code := v.FieldByName("code")

    fmt.Printf("name: canaddr==%v,canset=%v\n", name.CanAddr(), name.CanSet())
    fmt.Printf("code: canaddr==%v,canset=%v\n", code.CanAddr(), code.CanSet())

    if name.CanSet() {
        name.SetString("Tom")
    }

    if code.CanAddr() {
        *(*int)(unsafe.Pointer(code.UnsafeAddr())) = 100
    }

    fmt.Printf("%+v\n", *p)
}

输出:

1
2
3
name: canaddr==true,canset=true
code: canaddr==true,canset=false
{Name:Tom code:100}

方法

动态调用方法,谈不上有多麻烦。只需按 In 列表准备好所需参数即可

 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
package main

import (
    "fmt"
    "reflect"
)

type X struct{}

func (X) Test(x, y int) (int, error) {
    return x + y, fmt.Errorf("err: %d", x+y)
}

func main() {
    var a X

    v := reflect.ValueOf(&a)
    m := v.MethodByName("Test")

    in := []reflect.Value{
        reflect.ValueOf(1),
        reflect.ValueOf(2),
    }

    out := m.Call(in)
    for _, v := range out {
        fmt.Println(v)
    }
}

输出:

1
2
3
err: 3

对于变参来说,用 CallSlice 要更方便一些

 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
package main

import (
    "fmt"
    "reflect"
)

type X struct{}

func (X) Format(s string, a ...interface{}) string {
    return fmt.Sprintf(s, a...)
}

func main() {
    var a X

    v := reflect.ValueOf(&a)
    m := v.MethodByName("Format")

    out := m.Call([]reflect.Value{
        reflect.ValueOf("%s = %d"),
        reflect.ValueOf("x"), // 所有参数都须处理
        reflect.ValueOf(100),
    })

    fmt.Println(out)

    out = m.CallSlice([]reflect.Value{
        reflect.ValueOf("%s = %d"),
        reflect.ValueOf([]interface{}{"x", 100}), // 仅一个 []interface{} 即可
    })

    fmt.Println(out)
}

输出:

1
2
[x = 100]
[x = 100]

无法调用非导出方法,甚至无法获取有效地址