反射
0x01 反射的基本概念
reflect.Type 是一个接口,它表示一个类型。用于操作类信息,只能读取。
reflect.Value 是一个接口,它表示一个值。用于操作值,部分值可以被修改。
reflect.Value.Type() ==> reflect.Type
Kind 是一个枚举类型,它表示一个类型的种类。用于判断操作对象的类型,是否是指针、数组、结构体等。
只有 Kind() == reflect.Struct 才有字段。
Elem() 方法用于获取指针指向的值。

Interface() 方法用于获取值的接口。
测试案例,获取结构体的类型和值
go
package myreflect
import (
"reflect"
"testing"
)
func TestBase01(t *testing.T) {
// 测试反射的基本概念
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
student := Student{Name: "张三", Age: 18}
t.Logf("student: %v", student)
studentValue := reflect.ValueOf(student)
t.Logf("studentValue: %v", studentValue)
studentType := reflect.TypeOf(student)
t.Logf("studentType: %v", studentType)
t.Logf("studentValue.Type(): %v", studentValue.Type())
t.Logf("studentValue.Kind(): %v", studentValue.Kind())
if studentValue.Kind() == reflect.Struct {
t.Logf("studentValue.Kind() == reflect.Struct")
}
if studentType.Kind() == reflect.Struct {
t.Logf("studentType.Kind() == reflect.Struct")
}
// 测试获取结构体的字段数量
t.Logf("studentType.NumField(): %v", studentType.NumField())
t.Logf("studentValue.NumField(): %v", studentValue.NumField())
for i := 0; i < studentType.NumField(); i++ {
field := studentType.Field(i)
t.Log("studentType.Field==>", i, field.Name, field.Type, field.Offset, field.Index, field.PkgPath, field.Tag.Get("json"))
// 测试获取结构体的字段值
fieldValue := studentValue.Field(i)
t.Logf("studentValue.Field(%v): %v", i, fieldValue.Interface())
// 测试获取结构体的字段值的类型
fieldType := fieldValue.Type()
t.Logf("studentValue.Field(%v).Type(): %v", i, fieldType)
}
s2 := &Student{Name: "李四", Age: 20}
s2Value := reflect.ValueOf(s2)
fieldPtr := s2Value
if s2Value.Kind() == reflect.Ptr {
t.Logf("s2Value.Kind() == reflect.Ptr")
fieldPtr = s2Value.Elem()
} else {
t.Logf("s2Value.Kind() != reflect.Ptr")
}
// 测试获取结构体的字段值的指针
t.Logf("s2Value.Elem(): %v", fieldPtr)
t.Logf("s2Value.Elem().Type(): %v", fieldPtr.Type())
t.Logf("s2Value.Elem().Kind(): %v", fieldPtr.Kind())
}
/**
01_test.go:16: student: {张三 18}
01_test.go:19: studentValue: {张三 18}
01_test.go:22: studentType: myreflect.Student
01_test.go:24: studentValue.Type(): myreflect.Student
01_test.go:26: studentValue.Kind(): struct
01_test.go:29: studentValue.Kind() == reflect.Struct
01_test.go:33: studentType.Kind() == reflect.Struct
01_test.go:37: studentType.NumField(): 2
01_test.go:38: studentValue.NumField(): 2
01_test.go:42: studentType.Field==> 0 Name string 0 [0] name
01_test.go:46: studentValue.Field(0): 张三
01_test.go:50: studentValue.Field(0).Type(): string
01_test.go:42: studentType.Field==> 1 Age int 16 [1] age
01_test.go:46: studentValue.Field(1): 18
01_test.go:50: studentValue.Field(1).Type(): int
01_test.go:58: s2Value.Kind() == reflect.Ptr
01_test.go:64: s2Value.Elem(): {李四 20}
01_test.go:65: s2Value.Elem().Type(): myreflect.Student
01_test.go:66: s2Value.Elem().Kind(): struct
01_test.go:69: fieldPtr.FieldByName("Name"): 李四
*/0x02 反射的修改操作
reflect.Value.Set() 方法用于设置值。
CanSet() 方法用于判断是否可以设置值。
Elem() 方法用于获取指针指向的值。指针类型的值可以被修改。但是指针类的修改时,需要使用 Elem() 方法获取指针指向的值。
测试案例,修改结构体的字段值
go
package myreflect
import (
"reflect"
"testing"
)
func TestBase02(t *testing.T) {
// 测试反射的基本概念
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
student := Student{Name: "张三", Age: 18}
t.Logf("student: %v", student)
studentValue := reflect.ValueOf(student)
t.Logf("studentValue: %v", studentValue)
if studentValue.FieldByName("Name").CanSet() {
t.Logf("studentValue.CanSet() == true")
studentValue.FieldByName("Name").SetString("李四")
} else {
t.Logf("studentValue.CanSet() == false")
}
t.Logf("student: %v", student)
s2 := &Student{Name: "李四", Age: 20}
s2Value := reflect.ValueOf(s2)
if s2Value.Kind() == reflect.Ptr {
t.Logf("s2Value.Kind() == reflect.Ptr")
s2Value = s2Value.Elem()
} else {
t.Logf("s2Value.Kind() != reflect.Ptr")
}
t.Logf("s2Value: %v", s2Value)
if s2Value.FieldByName("Age").CanSet() {
t.Logf("s2Value.CanSet() == true")
s2Value.FieldByName("Age").Set(reflect.ValueOf(21))
} else {
t.Logf("s2Value.CanSet() == false")
}
t.Logf("s2: %v", s2)
}
/**
02_test.go:15: student: {张三 18}
02_test.go:17: studentValue: {张三 18}
02_test.go:23: studentValue.CanSet() == false
02_test.go:25: student: {张三 18}
02_test.go:30: s2Value.Kind() == reflect.Ptr
02_test.go:36: s2Value: {李四 20}
02_test.go:38: s2Value.CanSet() == true
02_test.go:44: s2: &{李四 21}
*/0x03 反射的调用方法操作
结构体方法接收器说明
以结构体作为输入,那么只能访问到结构体作为接收器的方法。
以指针作为输入,那么可以访问到任何接收器的方法
Method.Func.Call() 方法用于调用方法。必须将接收者实例作为第一个参数传入参数切片中。
Method.Call() 方法用于调用方法。系统会自动将接收者实例作为第一个参数传入参数切片中。
测试案例,调用方法
go
package myreflect
import (
"reflect"
"strconv"
"testing"
)
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func (s *Student) GetInfo() string {
return s.Name + "的年龄是" + strconv.Itoa(s.Age)
}
func TestBase03(t *testing.T) {
// 注意这里的studentValue是结构体指针,需要传递结构体指针,他可以访问到任何接收器的方法
// 以指针作为输入,那么可以访问到任何接收器的方法。
// 以结构体作为输入,那么只能访问到结构体作为接收器的方法。
student := &Student{Name: "张三", Age: 18}
t.Logf("student: %v", student)
studentType := reflect.TypeOf(student)
t.Logf("studentType: %v", studentType)
studentValue := reflect.ValueOf(student)
t.Logf("studentValue: %v", studentValue)
// 测试调用方法
m2 := studentValue.MethodByName("GetInfo")
t.Logf("m2: %v", m2)
res2 := m2.Call([]reflect.Value{})
t.Logf("res2: %v", res2)
m1, ok := studentType.MethodByName("GetInfo")
if !ok {
t.Fatalf("studentType.MethodByName('GetInfo') failed")
}
t.Logf("m1: %v", m1)
// 调用方法时,需要传递结构体指针
res := m1.Func.Call([]reflect.Value{studentValue})
t.Logf("res: %v", res)
}
/**
03_test.go:20: student: &{张三 18}
03_test.go:22: studentType: *myreflect.Student
03_test.go:24: studentValue: &{张三 18}
03_test.go:28: m2: 0x7ff6f2c0fe60
03_test.go:30: res2: [张三的年龄是18]
03_test.go:36: m1: {GetInfo func(*myreflect.Student) string <func(*myreflect.Student) string Value> 0}
03_test.go:39: res: [张三的年龄是18]
*/