Golang 反射系统
概要
Golang 的类型系统包括 泛型 (Generics)、类型转换 (Type Conversion) 和 反射 (Reflection)。其核心关系如下:
- Interface:是方法的集合,用于实现多态。
- Type:表示变量的数据类型,包括基本类型、结构体、接口等。
- Value:表示变量的具体值。
- Reflect:通过
reflect
包动态操作 Type 和 Value,包括reflect.TypeOf()
和reflect.ValueOf()
。 - 泛型:通过类型参数(如
T any
)实现类型安全且可复用的函数与结构体。
关键概念
1. Interface、Type、Value 的关系
interface{}
:空接口可以存储任何类型。- interface有两个值,一个是value的type,一个是实际的value。
reflect.TypeOf(v)
:返回变量v
的 类型。reflect.ValueOf(v)
:返回变量v
的 值,并提供修改 和调用的方法。
2. 泛型系统
- 泛型使用
type T any
定义类型参数。 any
相当于interface{}
,但可用于泛型。
分析一个struct的type和value
struct/interface
- Type
reflect.TypeOf(v)
Name()
类型名Kind()
底层基本类型(如struct、interface、)NumField()
字段数量Field(i int)
返回第 i 个字段的StructField
StructField.Name
:字段名。StructField.Type
:字段类型。StructField.Tag
:标签(如 JSON 标签)。StructField.Anonymous
:是否为匿名字段。
FieldByName(name)
根据字段名返回StructField
Elem
如果类型是指针,返回它指向的类型- 必须作用在指针、数组、切片、映射、通道上。如果对非指针类型调用
Elem()
,会panic。
- 必须作用在指针、数组、切片、映射、通道上。如果对非指针类型调用
- Value
reflect.ValueOf(v)
NumField()
返回结构体的字段数量Field(i int)
返回第 i 个字段的Value
FieldByName(name)
根据字段名返回字段的Value
Elem()
如果变量是指针,返回其指向的值CanSet()
判断变量是否可以被修改,true即为可修改Set()
设置变量的值(需要变量是可修改的,CanSet()
返回true)SetInt()
、SetString()
设置整数或字符串字段的值
举例
import (
"fmt"
"reflect"
)
type Name struct {
NickName string
}
type Person struct {
Name Name
Age int
}
func printReflectionInfo(i interface{}) {
t := reflect.TypeOf(i)
v := reflect.ValueOf(i)
fmt.Println("Type:", t)
fmt.Println("Type.Name:", t.Name())
fmt.Println("Type.Kind:", t.Kind())
fmt.Println("Type.NumField:", t.NumField())
//fmt.Println("Type.Elem:", t.Elem())
fmt.Println("Value:", v)
fmt.Println("Value.NumField:", v.NumField())
fmt.Println("Value.CanSet:", v.CanSet())
//fmt.Println("Value.Elem:", v.Elem())
if t.Kind() == reflect.Struct {
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
fmt.Printf("FieldName: %s, Type: %v, Value: %v, Kind: %v\n", field.Name, field.Type, value, value.Kind())
}
}
}
func main() {
p := Person{Name: Name{NickName: "Alice"}, Age: 30}
printReflectionInfo(p)
}
输出
Type: main.Person
Type.Name: Person
Type.Kind: struct
Type.NumField: 2
Value: {{Alice} 30}
Value.NumField: 2
Value.CanSet: false
FieldName: Name, Type: main.Name, Value: {Alice}, Kind: struct
FieldName: Age, Type: int, Value: 30, Kind: int
案例分析
Case 1: 遍历 struct 中的字段,找到类型为 X
的字段并修改其值
需求:
- 遍历结构体的所有字段。
- 判断字段的类型是否为
Name
。 - 如果是,修改其值。
package main
import (
"fmt"
"reflect"
)
type Name struct {
NickName string
}
type Person struct {
Name Name
Age int
}
func modifyNameField(v interface{}, newValue Name) {
val := reflect.ValueOf(v).Elem() // 获取指针指向的值
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
if field.Type() == reflect.TypeOf(Name{}) {
field.Set(reflect.ValueOf(newValue)) // 修改字段值
}
}
}
func main() {
p := Person{Name: Name{NickName: "Alice"}, Age: 30}
fmt.Println("修改前:", p)
modifyNameField(&p, Name{NickName: "Bob"})
fmt.Println("修改后:", p)
}
输出:
修改前: {Alice 30}
修改后: {Bob 30}
注意:
- 传递指针以允许修改结构体。
- 使用
Elem()
获取指针指向的值。 - 使用
Set()
修改值。
Case 2: 判断 interface{}
是否为某个 struct,并进行类型转换
需求:
- 接收一个
interface{}
参数。 - 判断它是否为
Person
类型。 - 如果是,则进行类型转换并打印其字段。
func checkAndPrintPerson(i interface{}) {
if p, ok := i.(Person); ok {
fmt.Println("这是 Person 类型:", p)
} else {
fmt.Println("不是 Person 类型")
}
}
func main() {
p := Person{Name: Name{NickName: "Alice"}, Age: 30}
checkAndPrintPerson(p)
var x int = 100
checkAndPrintPerson(x)
}
输出:
这是 Person 类型: {Alice 30}
不是 Person 类型
要点:
- 使用类型断言
.(Person)
进行类型检查和转换。 - 类型断言返回两个值:转换后的值和是否成功的布尔值。
总结
- 泛型 提供类型安全且灵活的编程方式。
- 类型转换 通过类型断言将
interface{}
转换为具体类型。 - 反射 通过
reflect
包动态获取和修改变量的类型和值。 - 常用方法包括
TypeOf()
、ValueOf()