Skip to content

第六章 反射 v1.0

在go中,反射是一种强大的工具,可以在运行时检查和修改程序的结构和行为。通过反射,我们可以动态地操纵像变量、数据结构、方法和类型这样的对象的各种属性和行为。

一、reflect包

reflect包的核心是两个类型,分别是:Type(类型信息)和Value(值信息)

1、获取反射对象

go
// 接收一个接口值,返回其静态类型的Type对象
func TypeOf(i interface{}) Type
go
// 接收一个接口值,返回其值的Value对象
func ValueOf(i interface{}) Value
go
package main

import (
	"fmt"
	"reflect"
)

func main() {
	var x int = 42
	t := reflect.TypeOf(x)  // 获取类型信息
	v := reflect.ValueOf(x) // 获取值信息

	fmt.Println("Type:", t)   // 输出:Type: int
	fmt.Println("Value:", v)  // 输出:Value: 42
}

2、类型信息(Type)

reflect.Type提供了获取类型元数据的方法

go
// 返回类型的Kind
func Kind() Kind
// 返回自定义类型的名称;非自定义类型返回空字符串
func Name() string
// 返回结构体类型的字段数量;非结构体类型会panic
func NumField() int
// 返回结构体类型的索引为i的字段;索引范围为[0, NumField()),超出索引范围或非结构体类型会导致panic
func Field(i int) StructField 
// 返回类型的方法数量
func NumMethod() int
// 返回类型的索引为i的方法;索引范围为[0, NumMethod()),超出索引范围会导致panic
func Method(int) Method
// 对指针、切片、映射等类型,返回其指向的元素类型
func Elem() Type

3、值信息(Value)

reflect.Value提供了读取和修改原始值的方法

go
// 返回类型的Kind
func Kind() Kind
// 将反射对象转换回接口值,再通过类型断言获取原始值
func Interface() interface{}
// 对指针类型的Value,返回其指向的元素的Value
func Elem() Value
// 返回结构体类型的字段数量;非结构体类型会panic
func NumField() int 
// 返回结构体类型的索引为i的字段;索引范围为[0, NumField()),超出索引范围或非结构体类型会导致panic
func Field(i int) Value
// 返回值是否可设置
func CanSet() bool
go
func Addr() Value
func Bool() bool
func Float() float64
func Int() int64
go
// 修改值需满足可设置的条件
func Set(x Value) 
func SetBool(x bool) 
func SetFloat(x float64) 
func SetInt(x int64)

要通过reflect.Value修改原始值,必须满足可设置的条件:

  • 反射对象由指针指向的变量创建(即ValueOf(&x)
  • 通过Elem()获取指针指向的元素
  • 被修改的字段/值必须是可导出的(结构体字段首字母大写)
go
func main() {
	var x int = 10
	v := reflect.ValueOf(&x) // 传入指针

	// 检查是否可设置
	fmt.Println("v.CanSet:", v.CanSet()) // false,此时v是指针,不可设置,需通过Elem()获取指向的值

	elem := v.Elem()         // 获取指针指向的元素(x)
	fmt.Println("elem.CanSet:", elem.CanSet()) // true

	elem.SetInt(20)          // 修改值
	fmt.Println("x =", x)    // 输出:x = 20
}

二、典型应用

go
func toJSON(v interface{}) string {
	t := reflect.TypeOf(v)
	val := reflect.ValueOf(v)

	// 处理指针
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
		val = val.Elem()
	}

	// 非结构体返回默认值
	if t.Kind() != reflect.Struct {
		return "{}"
	}

	result := "{"
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fieldVal := val.Field(i)

		// 读取json标签,默认用字段名
		jsonKey := field.Tag.Get("json")
		if jsonKey == "" {
			jsonKey = field.Name
		}

		// 拼接键值对
		switch fieldVal.Kind() {
		case reflect.String:
			result += fmt.Sprintf(`"%s":"%s"`, jsonKey, fieldVal.String())
		case reflect.Int:
			result += fmt.Sprintf(`"%s":%d`, jsonKey, fieldVal.Int())
		}

		if i < t.NumField()-1 {
			result += ","
		}
	}
	result += "}"
	return result
}
go
func validate(v interface{}) error {
	val := reflect.ValueOf(v)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}
	if val.Kind() != reflect.Struct {
		return fmt.Errorf("not a struct")
	}

	t := val.Type()
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fieldVal := val.Field(i)
		// 检查validate标签
		if tag := field.Tag.Get("validate"); tag == "required" {
			// 简单判断是否为空
			if fieldVal.Kind() == reflect.String && fieldVal.String() == "" {
				return fmt.Errorf("field %s is required", field.Name)
			}
		}
	}
	return nil
}

type User struct {
	Name string `validate:"required"`
	Age  int
}

func main() {
	u := User{Age: 20} // Name为空
	if err := validate(&u); err != nil {
		fmt.Println("Validation error:", err) // 输出:field Name is required
	}
}