反射是把双刃剑,功能强大但代码可读性并不理想,若非必要并不推荐使用反射。
1. 反射概念
反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
Go 语言中的反射是由 reflect 包提供支持的,它定义了两个重要的类型 Type 和 Value 任意接口值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两部分组成,并且 reflect 包提供了 reflect.TypeOf 和 reflect.ValueOf 两个函数来获取任意对象的 Value 和 Type 。
2. 获取类型信息
在 Go 语言中通过调用 reflect.TypeOf 函数,我们可以从一个任何非接口类型的值创建一个 reflect.Type 值。 reflect.Type 值表示着此非接口值的类型。通过此值,我们可以得到很多此非接口类型的信息。
当然,我们也可以将一个接口值传递给一个 reflect.TypeOf 函数调用,但是此调用将返回一个表示着此接口值的动态类型的 reflect.Type 值。
实际上, reflect.TypeOf 函数的唯一参数的类型为 interface{} , reflect.TypeOf 函数将总是返回一个表示着此唯一接口参数值的动态类型的 reflect.Type 值。
使用 reflect.TypeOf() 函数可以获得任意值的类型对象( reflect.Type ),程序通过类型对象可以访问任意值的类型信息。下面通过例子来理解获取类型对象的过程:
package main
import (
"fmt"
"reflect"
)
func main() {
var a int
// 通过 reflect.TypeOf() 取得变量 a 的类型对象 typeOfA,类型为 reflect.Type()
typeOfA := reflect.TypeOf(a)
// 通过 typeOfA 类型对象的成员函数,可以分别获取到 typeOfA 变量的类型名为 int,种类(Kind)为 int
fmt.Println(typeOfA.Name(), typeOfA.Kind()) // int int
}
func main() {
s := "hello"
sv := reflect.ValueOf(s)
st := reflect.TypeOf(s)
fmt.Println(sv, st) // hello string
}
2.1 反射的类型(Type)与种类(Kind)
在使用反射时,需要首先理解类型( Type )和种类( Kind )的区别。编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类( Kind )。例如,需要统一判断类型中的指针时,使用种类( Kind )信息就较为方便。
反射种类(Kind)的定义:
Go 程序中的类型( Type )指的是系统原生数据类型,如 int 、 string 、 bool 、 float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型。
种类( Kind )指的是对象归属的品种,在 reflect 包中有如下定义:
type Kind uint
const (
Invalid Kind = iota // 非法类型
Bool // 布尔型
Int // 有符号整型
Int8 // 有符号8位整型
Int16 // 有符号16位整型
Int32 // 有符号32位整型
Int64 // 有符号64位整型
Uint // 无符号整型
Uint8 // 无符号8位整型
Uint16 // 无符号16位整型
Uint32 // 无符号32位整型
Uint64 // 无符号64位整型
Uintptr // 指针
Float32 // 单精度浮点数
Float64 // 双精度浮点数
Complex64 // 64位复数类型
Complex128 // 128位复数类型
Array // 数组
Chan // 通道
Func // 函数
Interface // 接口
Map // 映射
Ptr // 指针
Slice // 切片
String // 字符串
Struct // 结构体
UnsafePointer // 底层指针
)
2.2 从类型对象中获取类型名称和种类
Go 语言中的类型名称对应的反射获取方法是 reflect.Type 中的 Name() 方法,返回表示类型名称的字符串。
类型归属的种类( Kind )使用的是 reflect.Type 中的 Kind() 方法,返回 reflect.Kind 类型的常量。
package main
import (
"fmt"
"reflect"
)
// 定义一个Enum类型
type Enum int
const (
Zero Enum = 0
)
func main() {
// 声明一个空结构体
type cat struct {
}
// 将 cat 实例化,并且使用 reflect.TypeOf() 获取被实例化后的 cat 的反射类型对象
typeOfCat := reflect.TypeOf(cat{})
// 显示反射类型对象的名称和种类
fmt.Println(typeOfCat.Name(), typeOfCat.Kind()) // cat struct
// 获取Zero常量的反射类型对象
typeOfA := reflect.TypeOf(Zero)
// 显示反射类型对象的名称和种类
fmt.Println(typeOfA.Name(), typeOfA.Kind()) // Enum int
}
3. 获取指针指向的元素类型
Go 语言程序中对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型。这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作,代码如下:
package main
import (
"fmt"
"reflect"
)
func main() {
// 声明一个空结构体
type cat struct {
}
// 创建cat的实例,ins 是一个 *cat 类型的指针变量
ins := &cat{}
// 获取结构体指针实例的反射类型对象
typeOfCat := reflect.TypeOf(ins)
// 输出指针变量的类型名称和种类
// Go 语言的反射中对所有指针变量的种类都是 Ptr,但注意,指针变量的类型名称是空,不是 *cat
fmt.Printf("name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())
// 取指针类型的元素类型,也就是 cat 类型。这个操作不可逆,不可以通过一个非指针类型获取它的指针类型
typeOfCat = typeOfCat.Elem()
// 显示反射类型对象的名称和种类
fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())
}
输出:
name:'' kind:'ptr'
element name: 'cat', element kind: 'struct'
4. 获取结构体成员类型
任意值通过 reflect.TypeOf() 获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的 NumField() 和 Field() 方法获得结构体成员的详细信息。
和 reflect.Value 不同,reflect.Type 是一个接口,而不是一个结构体,所以也只能使用它的方法。
以下是 reflect.Type 接口常用的方法。从这个列表来看,大部分都和 reflect.Value 的方法功能相同。
type Type interface {
Implements(u Type) bool
AssignableTo(u Type) bool
ConvertibleTo(u Type) bool
Comparable() bool
//以下这些方法和Value结构体的功能相同
Kind() Kind
Method(int) Method
MethodByName(string) (Method, bool)
NumMethod() int
Elem() Type
Field(i int) StructField
FieldByIndex(index []int) StructField
FieldByName(name string) (StructField, bool)
FieldByNameFunc(match func(string) bool) (StructField, bool)
NumField() int
}
其中几个特有的方法如下:
-
Implements方法用于判断是否实现了接口u; -
AssignableTo方法用于判断是否可以赋值给类型u,其实就是是否可以使用=,即赋值运算符; -
ConvertibleTo方法用于判断是否可以转换成类型u,其实就是是否可以进行类型转换; -
Comparable方法用于判断该类型是否是可比较的,其实就是是否可以使用关系运算符进行比较。
与成员获取相关的 reflect.Type 的方法如下表所示。结构体成员访问的方法列表:
| 方法 | 说明 |
|---|---|
| Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息。当值不是结构体或索引超界时发生宕机 |
| NumField() int | 返回结构体成员字段数量。当类型不是结构体或索引超界时发生宕机 |
| FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息。没有找到时 bool 返回 false,当类型不是结构体或索引超界时发生宕机 |
| FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值。当类型不是结构体或索引超界时 发生宕机 |
| FieldByNameFunc( match func(string) bool) (StructField,bool) | 根据匹配函数匹配需要的字段。当值不是结构体或索引超界时发生宕机 |
4.1 结构体字段类型
reflect.Type 的 Field() 方法返回 StructField 结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(Struct Tag)等,而且还可以通过 StructField 的 Type 字段进一步获取结构体成员的类型信息。StructField 的结构如下:
type StructField struct {
Name string // 字段名
PkgPath string // 字段在结构体中的路径
Type Type // 字段本身的反射类型对象,类型为 reflect.Type,
Tag StructTag // 结构体标签,为结构体字段标签的额外信息,可以单独提取
Offset uintptr // 字段在结构体中的相对偏移
Index []int // Type.FieldByIndex中的返回的索引值
Anonymous bool // 是否为匿名字段
}
4.2 遍历结构体的字段和方法
可以通过 NumField 方法获取结构体字段的数量,然后使用 for 循环,通过 Field 方法就可以遍历结构体的字段,并打印出字段名称。同理,遍历结构体的方法也是同样的思路,代码也类似,如下所示:
package main
import (
"fmt"
"reflect"
)
type person struct {
Name string
Age int
}
func (p person) String() string {
return fmt.Sprintf("Name is %s,Age is %d", p.Name, p.Age)
}
func main() {
p := person{Name: "wohu", Age: 18}
pt := reflect.TypeOf(p)
//遍历person的字段
for i := 0; i < pt.NumField(); i++ {
fmt.Println("字段:", pt.Field(i).Name)
}
//遍历person的方法
for i := 0; i < pt.NumMethod(); i++ {
fmt.Println("方法:", pt.Method(i).Name)
}
}
4.3 获取成员反射信息
反射访问结构体成员类型及信息:
package main
import (
"fmt"
"reflect"
)
func main() {
// 声明一个空结构体
type cat struct {
Name string
// 带有结构体tag的字段
Type int `json:"type" id:"100"`
// `json:"type" id:"100"` 这个字符串在 Go 语言中被称为 Tag(标签)。
// 一般用于给字段添加自定义信息,方便其他模块根据信息进行不同功能的处理。
}
// 创建cat的实例
ins := cat{Name: "mimi", Type: 1}
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(ins)
// 遍历结构体所有成员
for i := 0; i < typeOfCat.NumField(); i++ {
// 获取每个成员的结构体字段类型
fieldType := typeOfCat.Field(i) // 使用 reflect.Type 的 Field() 方法返回的结构不再是 reflect.Type 而是StructField 结构体。
// 输出成员名和tag
fmt.Printf("name: %v tag: '%v'\n", fieldType.Name, fieldType.Tag)
}
// 通过字段名, 找到字段类型信息
if catType, ok := typeOfCat.FieldByName("Type"); ok {
// 从tag中取出需要的tag
fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
}
}
输出结果:
name: Name tag: ''
name: Type tag: 'json:"type" id:"100"'
type 100
5. 获取结构体成员的值
reflect.Value 可以通过函数 reflect.ValueOf 获得,下面我将为你介绍它的结构和用法。
在 Go 语言中,reflect.Value 被定义为一个 struct 结构体,它的定义如下面的代码所示:
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}
我们发现 reflect.Value 结构体的字段都是私有的,也就是说,我们只能使用 reflect.Value 的方法。现在看看它有哪些常用方法,如下所示:
//针对具体类型的系列方法
//以下是用于获取对应的值
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
看着比较多,其实就三类:
- 一类用于获取和修改对应的值;
- 一类和 struct 类型的字段有关,用于获取对应的字段;
- 一类和类型上的方法集有关,用于获取对应的方法;
5.1 获取原始类型
在上面的例子中,我通过 reflect.ValueOf 函数把任意类型的对象转为一个 reflect.Value,而如果想逆向转回来也可以,reflect.Value 为我们提供了 Inteface 方法,如下面的代码所示:
func main() {
s := "hello"
sv := reflect.ValueOf(s)
ss := sv.Interface().(string)
fmt.Println(sv, ss) // hello hello
}
5.2 修改对应的值
已经定义的变量可以通过反射在运行时修改,比如上面的示例 s=“hello”,修改为 “world”, 如下所示:
func main() {
s := "hello"
sv := reflect.ValueOf(&s) // 必须是&s,否则报错
// panic: reflect: call of reflect.Value.Elem on string Value
sv.Elem().SetString("world")
fmt.Println(s) // world
}
这样就通过反射修改了一个变量。因为 reflect.ValueOf 函数返回的是一份值的拷贝,所以我们要传入变量的指针才可以。 因为传递的是一个指针,所以需要调用 Elem 方法找到这个指针指向的值,这样才能修改。 最后我们就可以使用 SetString 方法修改值了。
要修改一个变量的值,有几个关键点:传递指针(可寻址),通过 Elem 方法获取指向的值,才可以保证值可以被修改,reflect.Value 为我们提供了 CanSet 方法判断是否可以修改该变量。
那么如何修改 struct 结构体字段的值呢?参考变量的修改方式,可总结出以下步骤:
-
传递一个
struct结构体的指针,获取对应的reflect.Value; -
通过
Elem方法获取指针指向的值; -
通过
Field方法获取要修改的字段; -
通过
Set系列方法修改成对应的值。
运行下面的代码,你会发现变量 p 中的 Name 字段已经被修改为张三了。
func main() {
p := person{Name: "wohu", Age: 18}
ppv := reflect.ValueOf(&p)
ppv.Elem().Field(0).SetString("张三")
fmt.Println(p) // {张三 18}
}
type person struct {
Name string
Age int
}
最后再来总结一下通过反射修改一个值的规则。
-
可被寻址,通俗地讲就是要向
reflect.ValueOf函数传递一个指针作为参数。 -
如果要修改
struct结构体字段值的话,该字段需要是可导出的,而不是私有的,也就是该字段的首字母为大写。 -
记得使用
Elem方法获得指针指向的值,这样才能调用Set系列方法进行修改。
记住以上规则,你就可以在程序运行时通过反射修改一个变量或字段的值。
5.3 获取对应的底层类型
底层类型是什么意思呢?其实对应的主要是基础类型,比如接口、结构体、指针…因为我们可以通过 type 关键字声明很多新的类型。
比如在上面的例子中,变量 p 的实际类型是 person,但是 person 对应的底层类型是 struct 这个结构体类型,而 &p 对应的则是指针类型。我们来通过下面的代码进行验证:
type person struct {
Name string
Age int
}
func main() {
p := person{Name: "wohu", Age: 18}
ppv := reflect.ValueOf(&p)
fmt.Println(ppv.Kind()) // ptr
pv := reflect.ValueOf(p)
fmt.Println(pv.Kind()) // struct
}
Kind 方法返回一个 Kind 类型的值,它是一个常量,具体可见 2.1 节
反射值对象(reflect.Value)提供对结构体访问的方法,通过这些方法可以完成对结构体任意值的访问,如下表所示。
| 方 法 | 备 注 | |
|---|---|---|
| Field(i int) Value | 根据索引,返回索引对应的结构体成员字段的反射值对象。当值不是结构体或索引超界时发生宕机 | |
| NumField() int | 返回结构体成员字段数量。当值不是结构体或索引超界时发生宕机 | |
| FieldByName(name string) Value | 根据给定字符串返回字符串对应的结构体字段。没有找到时返回零值,当值不是结构体或索引超界时发生宕机 | |
| FieldByIndex(index []int) Value | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的值。 没有找到时返回零值,当值不是结构体或索引超界时发生宕机 | |
| FieldByNameFunc(match func(string) bool) Value | 根据匹配函数匹配需要的字段。找到时返回零值,当值不是结构体或索引超界时发生宕机 |
下面代码构造一个结构体包含不同类型的成员。通过 reflect.Value 提供的成员访问函数,可以获得结构体值的各种数据。
package main
import (
"fmt"
"reflect"
)
// 定义结构体
type dummy struct {
a int
b string
// 嵌入字段
float32
bool
next *dummy
}
func main() {
// 值包装结构体
d := reflect.ValueOf(dummy{
next: &dummy{},
})
// 获取字段数量
fmt.Println("NumField", d.NumField())
// 获取索引为2的字段(float32字段)
floatField := d.Field(2)
// 输出字段类型
fmt.Println("Field", floatField.Type())
// 根据名字查找字段
fmt.Println("FieldByName(\"b\").Type", d.FieldByName("b").Type())
// 根据索引查找值中, next字段的int字段的值
fmt.Println("FieldByIndex([]int{4, 0}).Type()", d.FieldByIndex([]int{4, 0}).Type())
}
6. Go 语言结构体标签
通过 reflect.Type 获取结构体成员信息 reflect.StructField 结构中的 Tag 被称为结构体标签(Struct Tag)。结构体标签是对结构体字段的额外信息标签。
6.1 结构体标签格式
Tag 在结构体字段后方书写的格式如下:
`key1:"value1" key2:"value2"`
结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。
6.2 从结构体标签中获取值
StructTag 拥有一些方法,可以进行 Tag 信息的解析和提取,如下所示:
func(tag StructTag)Get(key string)string
根据Tag中的键获取对应的值,例如key1:"value1"key2:"value2"的 Tag 中,可以传入“key1”获得“value1”。func(tag StructTag)Lookup(key string)(value string,ok bool)
根据 Tag 中的键,查询值是否存在

6.3 结构体标签格式错误导致的问题
编写 Tag 时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,参见下面这个例子:
package main
import (
"fmt"
"reflect"
)
func main() {
type cat struct {
Name string
Type int `json: "type" id:"100"`
}
typeOfCat := reflect.TypeOf(cat{})
if catType, ok := typeOfCat.FieldByName("Type"); ok {
fmt.Println(catType.Tag.Get("json"))
}
}
代码输出空字符串,并不会输出期望的 type。
第 12 行中,在json:和"type"之间增加了一个空格。这种写法没有遵守结构体标签的规则,因此无法通过 Tag.Get 获取到正确的 json 对应的值。
7. 反射获取值信息
当我们将一个接口值传递给一个 reflect.ValueOf 函数调用时,此调用返回的是代表着此接口值的动态值的一个 reflect.Value 值。我们必须通过间接的途径获得一个代表一个接口值的 reflect.Value 值。
一个 reflect.Value 值的 CanSet 方法将返回此 reflect.Value 值代表的 Go 值是否可以被修改(可以被赋值)。如果一个 Go 值可以被修改,则我们可以调用对应的 reflect.Value 值的 Set 方法来修改此 Go 值。
注意:reflect.ValueOf 函数直接返回的 reflect.Value 值都是不可修改的。
反射不仅可以获取值的类型信息,还可以动态地获取或者设置变量的值。Go 语言中使用 reflect.Value 获取和设置变量的值。
7.1 使用反射值对象包装任意值
Go 语言中,使用 reflect.ValueOf() 函数获得值的反射值对象(reflect.Value)。书写格式如下:
value := reflect.ValueOf(rawValue)
reflect.ValueOf 返回 reflect.Value 类型,包含有 rawValue 的值信息。 reflect.Value 与原值间可以通过值包装和值获取互相转化。 reflect.Value 是一些反射操作的重要类型,如反射调用函数。
7.2 从反射值对象获取被包装的值
可以通过下面几种方法从反射值对象 reflect.Value 中获取原值,如下表所示。
| 方法名 | 说 明 |
|---|---|
| Interface() interface {} | 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型 |
| Int() int64 | 将值以 int 类型返回,所有有符号整型均可以此方式返回 |
| Uint() uint64 | 将值以 uint 类型返回,所有无符号整型均可以此方式返回 |
| Float() float64 | 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回 |
| Bool() bool | 将值以 bool 类型返回 |
| Bytes() []bytes | 将值以字节数组 []bytes 类型返回 |
| String() string | 将值以字符串类型返回 |
下面代码中,将整型变量中的值使用 reflect.Value 获取反射值对象(reflect.Value)。再通过 reflect.Value 的 Interface() 方法获得 interface{} 类型的原值,通过 int 类型对应的 reflect.Value 的 Int() 方法获得整型值。
package main
import (
"fmt"
"reflect"
)
func main() {
// 声明整型变量a并赋初值
var a int = 1024
// 获取变量a的反射值对象
valueOfA := reflect.ValueOf(a)
// 获取interface{}类型的值, 通过类型断言转换
var getA int = valueOfA.Interface().(int)
// 获取64位的值, 强制类型转换为int类型
var getA2 int = int(valueOfA.Int())
fmt.Println(getA, getA2) // 1024 1024
}
8. 判断反射值的空和有效性
反射值对象( reflect.Value )提供一系列方法进行零值和空判定,如下表所示。
| 方 法 | 说 明 |
|---|---|
| IsNil() bool | 返回值是否为 nil。如果值类型不是通道(channel)、函数、接口、map、指针或 切片时发生 panic,类似于语言层的v== nil操作 |
| IsValid() bool | 判断值是否有效。 当值本身非法时,返回 false,例如 reflect Value不包含任何值,值为 nil 等。 |
下面的例子将会对各种方式的空指针进行 IsNil() 和 IsValid() 的返回值判定检测。同时对结构体成员及方法查找 map 键值对的返回值进行 IsValid() 判定,参考下面的代码。
package main
import (
"fmt"
"reflect"
)
func main() {
// *int的空指针
var a *int
fmt.Println("var a *int:", reflect.ValueOf(a).IsNil())
// nil值
fmt.Println("nil:", reflect.ValueOf(nil).IsValid())
// *int类型的空指针
fmt.Println("(*int)(nil):", reflect.ValueOf((*int)(nil)).Elem().IsValid())
// 实例化一个结构体
s := struct{}{}
// 尝试从结构体中查找一个不存在的字段
fmt.Println("不存在的结构体成员:", reflect.ValueOf(s).FieldByName("").IsValid())
// 尝试从结构体中查找一个不存在的方法
fmt.Println("不存在的结构体方法:", reflect.ValueOf(s).MethodByName("").IsValid())
// 实例化一个map
m := map[int]int{}
// 尝试从map中查找一个不存在的键
fmt.Println("不存在的键:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
}
9. 通过反射修改变量的值
10. 通过类型信息创建实例
11. 通过反射调用函数
12. 反射定律
反射是计算机语言中程序检视其自身结构的一种方法,它属于元编程的一种形式。反射灵活、强大,但也存在不安全。它可以绕过编译器的很多静态检查,如果过多使用便会造成混乱。为了帮助开发者更好地理解反射,Go 语言的作者在博客上总结了反射的三大定律。
-
任何接口值 interface{} 都可以反射出反射对象,也就是 reflect.Value 和 reflect.Type,通过函数 reflect.ValueOf 和 reflect.TypeOf 获得。
-
反射对象也可以还原为 interface{} 变量,也就是第 1 条定律的可逆性,通过 reflect.Value 结构体的 Interface 方法获得。
-
要修改反射的对象,该值必须可设置,也就是可寻址,参考上节课修改变量的值那一节的内容理解。
小提示:任何类型的变量都可以转换为空接口 intferface{},所以第 1 条定律中函数 reflect.ValueOf 和 reflect.TypeOf 的参数就是 interface{},表示可以把任何类型的变量转换为反射对象。在第 2 条定律中,reflect.Value 结构体的 Interface 方法返回的值也是 interface{},表示可以把反射对象还原为对应的类型变量。
一旦你理解了这三大定律,就可以更好地理解和使用 Go 语言反射。
总结
在反射中,reflect.Value 对应的是变量的值,如果你需要进行和变量的值有关的操作,应该优先使用 reflect.Value,比如获取变量的值、修改变量的值等。reflect.Type 对应的是变量的类型,如果你需要进行和变量的类型本身有关的操作,应该优先使用 reflect.Type,比如获取结构体内的字段、类型拥有的方法集等。
此外我要再次强调:反射虽然很强大,可以简化编程、减少重复代码,但是过度使用会让你的代码变得复杂混乱。所以除非非常必要,否则尽可能少地使用它们。
本文深入讲解Go语言反射机制,涵盖反射概念、获取类型信息、结构体成员类型获取、结构体标签解析、反射值信息获取等内容,助您掌握反射在Go语言中的应用。
1286

被折叠的 条评论
为什么被折叠?



