Go 语言提供了一种机制,能够在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,而不需要在编译时就知道这些变量的具体类型。这种机制被称为 反射

但反射是把双刃剑,功能强大但代码可读性并不理想,若非必要并不推荐使用反射。

在 Go 中, ​​reflect​​​ 包实现了运行时反射。​​reflect​​​ 包会帮助识别 ​​interface{}​​ 变量的底层具体类型和具体值。


Go 语言系列37:反射_程序运行

reflect.Type

Go 语言系列37:反射_字段_02


​reflect.Type​​​ 表示 ​​interface{}​​​ 的具体类型。​​reflect.TypeOf()​​​ 方法返回 ​​reflect.Type​​ 。

像我们之前讲过的空接口参数的函数,可以通过类型断言来判断传入变量的类型,也可以借助反射来确定传入变量的类型。

package main

import (
"fmt"
"reflect"
)

func reflectType(x interface{}) {
obj := reflect.TypeOf(x)
fmt.Println(obj)
}

func main() {
var a int64 = 123
reflectType(a)
var b float64 = 3.14
reflectType(b)
}

该程序运行后输出结果如下:

int64
float64

Go 语言系列37:反射_程序运行

reflect.Value

Go 语言系列37:反射_字段_02


​reflect.Value​​​ 表示 ​​interface{}​​​ 的具体值。​​reflect.ValueOf()​​​ 方法返回 ​​reflect.Value​​ 。

package main

import (
"fmt"
"reflect"
)

func reflectType(x interface{}) {
typeX := reflect.TypeOf(x)
valueX := reflect.ValueOf(x)
fmt.Println(typeX)
fmt.Println(valueX)
}

func main() {
var a int64 = 123
reflectType(a)
var b float64 = 3.14
reflectType(b)
}

该程序运行后输出结果如下:

int64
123
float64
3.14

Go 语言系列37:反射_程序运行

relfect.Kind

Go 语言系列37:反射_字段_02


​relfect.Kind​​ 表示的是种类。在使用反射时,需要理解类型(Type)和种类(Kind)的区别。编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。

Go 语言程序中的类型(Type)指的是系统原生数据类型,如 ​​int​​​ 、 ​​string​​​ 、 ​​bool​​​ 、 ​​float32​​​ 等类型,以及使用 ​​type​​​ 关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 ​​type A struct{}​​​ 定义结构体时,​​A​​​ 就是 ​​struct{}​​ 的类型。

种类(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
Ptr
Slice
String
Struct
UnsafePointer
)

通过下面这个程序,相信你会很容易明白这两者的区别:

package main

import (
"fmt"
"reflect"
)

func reflectType(x interface{}) {
typeX := reflect.TypeOf(x)
fmt.Println(typeX.Kind()) // struct
fmt.Println(typeX) // main.cat
}

type cat struct {

}

func main() {
var a cat
reflectType(a)
}

该程序运行后输出结果如下:

struct
main.cat

Go 语言系列37:反射_程序运行

relfect.NumField()

Go 语言系列37:反射_字段_02


​relfect.NumField()​​ 方法返回结构体中字段的数量。

package main

import (
"fmt"
"reflect"
)

func reflectNumField(x interface{}) {
// 检查 x 的类别是 struct
if reflect.ValueOf(x).Kind() == reflect.Struct {
v := reflect.ValueOf(x)
fmt.Println("Number of fields", v.NumField())
}
}

type cat struct {
name string
age int
}

func main() {
var a cat
reflectNumField(a)
}

该程序运行后输出结果如下:

Number of fields 2

Go 语言系列37:反射_程序运行

relfect.Field()

Go 语言系列37:反射_字段_02


​relfect.Field(i int)​​​ 方法返回字段 ​​i​​​ 的 ​​reflect.Value​​ 。

package main

import (
"fmt"
"reflect"
)

func reflectNumField(x interface{}) {
// 检查 x 的类别是 struct
if reflect.ValueOf(x).Kind() == reflect.Struct {
v := reflect.ValueOf(x)
fmt.Println("Number of fields", v.NumField())
for i := 0; i < v.NumField(); i++ {
fmt.Printf("Field:%d type:%T value:%v\n", i, v.Field(i), v.Field(i))
}
}
}

type cat struct {
name string
age int
}

func main() {
var a = cat{"哆啦A梦", 10}
reflectNumField(a)
}

该程序运行后输出结果如下:

Number of fields 2
Field:0 type:reflect.Value value:哆啦A梦
Field:1 type:reflect.Value value:10


参考文献:

[1] Alan A. A. Donovan; Brian W. Kernighan, Go 程序设计语言, Translated by 李道兵, 高博, 庞向才, 金鑫鑫 and 林齐斌, 机械工业出版社, 2017.