Skip to content

反射

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]
*/

最后更新: