Skip to content

什么是反射

简单来说, 反射就是: 在程序运行时, 动态地获取变量的类型信息和结构信息, 并能动态调用方法、修改属性

推荐阅读:

反射的三大法则

  • 从接口值到反射对象: 使用 reflect.TypeOf()reflect.ValueOf()
  • 从反射对象到接口值: 使用 reflect.Value.Interface() 方法
  • 要修改反射对象, 其值必须是可设置的(Settable): 这意味着你通常需要传递指针而不是值
go
package main

import (
	"fmt"
	"reflect"
)

func reflectType(x interface{}) {
	v := reflect.TypeOf(x)
	fmt.Printf("type:%v\n", v)
}
func main() {
	var a float32 = 3.14
	reflectType(a) // type:float32

	var b int64 = 100
	reflectType(b) // type:int64
}

反射的优缺点

由于缺点非常明显, 不到必要, 不要滥用

优点

  • 通用性 :可以编写处理任意类型的函数(如通用的 Print、Clone、DeepEqual)
  • 框架开发:是 ORM (Gorm)、Web 框架 (Gin)、序列化 (JSON) 的基石
  • 动态性 :可以在运行时动态决定调用哪个方法或访问哪个字段

缺点

  • 性能开销 : 反射涉及动态类型查找,比直接代码慢很多(通常慢 10-100 倍)
  • 类型安全丢失: 编译期无法检查错误,错误会推迟到运行时 panic
  • 代码难读 : 反射代码通常晦涩难懂,维护成本高
  • 不可导出字段: 反射无法修改小写字母开头的私有字段(会 panic)
go
package main

import (
	"fmt"
	"reflect"
	"strings"
)

type User struct {
	ID       int    `json:"userId"`
	Name     string `json:"userName"`
	Email    string `json:"email"`
	Password string `json:"password"`
}

func main() {
	mapData := StructToMap(User{
		ID:       1001,
		Name:     "secret",
		Email:    "secret@example.com",
		Password: "123456",
	})

	fmt.Println(mapData)
}

func StructToMap(obj interface{}) map[string]interface{} {
	result := make(map[string]interface{})
	v := reflect.ValueOf(obj)
	t := reflect.TypeOf(obj)

	// 如果是指针,获取其指向的元素
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
		t = t.Elem()
	}

	if v.Kind() != reflect.Struct {
		return result
	}

	for i := 0; i < v.NumField(); i++ {
		field := t.Field(i)
		value := v.Field(i)

		// 跳过未导出的字段
		if !value.CanInterface() {
			continue
		}

		// 优先使用 json tag,如果没有则使用字段名
		tag := field.Tag.Get("json")
		if tag == "" || tag == "-" {
			tag = field.Name
		} else {
			// 处理 json:"name,omitempty" 中的逗号
			if idx := strings.Index(tag, ","); idx != -1 {
				tag = tag[:idx]
			}
		}

		result[tag] = value.Interface()
	}
	return result
}

Released under the MIT License.