反射
反射是在运行时检查和操作类型的能力。强大但要谨慎使用!我刚开始学反射的时候,觉得特别神奇,什么都想用反射来实现。但后来在实际项目中才明白,反射是把双刃剑,性能开销大、代码可读性差,能不用就不用。通常只有在写框架、序列化这类通用工具时才会用到。
什么是反射?
反射让你可以在运行时:
- 检查变量的类型
- 读取和修改变量的值
- 调用方法
- 创建新的类型实例
反射的代价
- 性能较差(比直接调用慢10-100倍)
- 代码可读性降低
- 编译时无法检查错误
原则:能不用就不用,必须用时才用。
reflect包基础
Type 和 Value
import "reflect"
var x float64 = 3.14
// 获取类型
t := reflect.TypeOf(x)
fmt.Println("Type:", t) // float64
fmt.Println("Kind:", t.Kind()) // float64
// 获取值
v := reflect.ValueOf(x)
fmt.Println("Value:", v) // 3.14
fmt.Println("Type:", v.Type()) // float64
fmt.Println("Kind:", v.Kind()) // float64
fmt.Println("Float:", v.Float()) // 3.14
Kind vs Type
type MyInt int
var x MyInt = 42
t := reflect.TypeOf(x)
fmt.Println("Type:", t) // main.MyInt(自定义类型名)
fmt.Println("Kind:", t.Kind()) // int(底层类型)
Kind是有限的基础类型:
const (
Invalid Kind = iota
Bool
Int
Int8, Int16, Int32, Int64
Uint, Uint8, Uint16, Uint32, Uint64
Float32, Float64
Complex64, Complex128
Array, Chan, Func, Interface, Map, Ptr, Slice, String, Struct
UnsafePointer
)
检查类型
检查结构体
type Person struct {
Name string `json:"name" validate:"required"`
Age int `json:"age" validate:"min=0,max=150"`
}
p := Person{Name: "张三", Age: 25}
t := reflect.TypeOf(p)
fmt.Println("类型名:", t.Name()) // Person
fmt.Println("包路径:", t.PkgPath()) // main
fmt.Println("字段数:", t.NumField()) // 2
// 遍历字段
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("字段: %s, 类型: %s, Tag: %s\n",
field.Name, field.Type, field.Tag)
}
// 输出:
// 字段: Name, 类型: string, Tag: json:"name" validate:"required"
// 字段: Age, 类型: int, Tag: json:"age" validate:"min=0,max=150"
读取标签
field, _ := t.FieldByName("Name")
jsonTag := field.Tag.Get("json")
validateTag := field.Tag.Get("validate")
fmt.Println("json tag:", jsonTag) // name
fmt.Println("validate tag:", validateTag) // required
读取和修改值
读取值
type Person struct {
Name string
Age int
}
p := Person{Name: "张三", Age: 25}
v := reflect.ValueOf(p)
// 读取字段值
name := v.FieldByName("Name").String()
age := v.FieldByName("Age").Int()
fmt.Println("Name:", name) // 张三
fmt.Println("Age:", age) // 25
修改值
要修改值,必须传入指针并使用 Elem() 获取指向的值:
p := Person{Name: "张三", Age: 25}
v := reflect.ValueOf(&p) // 传入指针!
elem := v.Elem() // 获取指向的值
// 修改字段
elem.FieldByName("Name").SetString("李四")
elem.FieldByName("Age").SetInt(30)
fmt.Println(p) // {李四 30}
判断是否可修改
v := reflect.ValueOf(x)
fmt.Println("CanSet:", v.CanSet()) // false
v = reflect.ValueOf(&x).Elem()
fmt.Println("CanSet:", v.CanSet()) // true
调用方法
type Calculator struct{}
func (c Calculator) Add(a, b int) int {
return a + b
}
func (c Calculator) Multiply(a, b int) int {
return a * b
}
func main() {
calc := Calculator{}
v := reflect.ValueOf(calc)
// 获取方法
addMethod := v.MethodByName("Add")
// 准备参数
args := []reflect.Value{
reflect.ValueOf(3),
reflect.ValueOf(4),
}
// 调用方法
result := addMethod.Call(args)
fmt.Println("3 + 4 =", result[0].Int()) // 7
}
创建新值
// 创建新的结构体
t := reflect.TypeOf(Person{})
v := reflect.New(t) // 返回 *Person
// 设置字段
elem := v.Elem()
elem.FieldByName("Name").SetString("张三")
elem.FieldByName("Age").SetInt(25)
// 转换为具体类型
p := v.Interface().(*Person)
fmt.Println(*p) // {张三 25}
// 创建切片
sliceType := reflect.SliceOf(reflect.TypeOf(0))
slice := reflect.MakeSlice(sliceType, 0, 10)
slice = reflect.Append(slice, reflect.ValueOf(1))
slice = reflect.Append(slice, reflect.ValueOf(2))
fmt.Println(slice.Interface()) // [1 2]
// 创建Map
mapType := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
m := reflect.MakeMap(mapType)
m.SetMapIndex(reflect.ValueOf("one"), reflect.ValueOf(1))
fmt.Println(m.Interface()) // map[one:1]
实战案例:简易JSON序列化
package main
import (
"fmt"
"reflect"
"strings"
)
func toJSON(v interface{}) string {
val := reflect.ValueOf(v)
typ := val.Type()
if val.Kind() != reflect.Struct {
return fmt.Sprintf("%v", v)
}
var parts []string
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
fieldVal := val.Field(i)
// 跳过未导出的字段
if !fieldVal.CanInterface() {
continue
}
// 获取json标签
name := field.Tag.Get("json")
if name == "" {
name = field.Name
}
if name == "-" {
continue
}
// 处理omitempty
if strings.Contains(name, ",omitempty") {
name = strings.Split(name, ",")[0]
if isZero(fieldVal) {
continue
}
}
// 格式化值
var valStr string
switch fieldVal.Kind() {
case reflect.String:
valStr = fmt.Sprintf(`"%s"`, fieldVal.String())
case reflect.Int, reflect.Int64:
valStr = fmt.Sprintf("%d", fieldVal.Int())
case reflect.Float64:
valStr = fmt.Sprintf("%f", fieldVal.Float())
case reflect.Bool:
valStr = fmt.Sprintf("%t", fieldVal.Bool())
default:
valStr = fmt.Sprintf("%v", fieldVal.Interface())
}
parts = append(parts, fmt.Sprintf(`"%s":%s`, name, valStr))
}
return "{" + strings.Join(parts, ",") + "}"
}
func isZero(v reflect.Value) bool {
return v.IsZero()
}
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Password string `json:"-"`
Email string `json:"email,omitempty"`
}
func main() {
user := User{
ID: 1,
Username: "zhangsan",
Password: "secret",
Email: "",
}
json := toJSON(user)
fmt.Println(json)
// {"id":1,"username":"zhangsan"}
}
实战案例:表单验证器
package main
import (
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
)
type ValidationError struct {
Field string
Message string
}
func (e ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
func Validate(v interface{}) []ValidationError {
var errors []ValidationError
val := reflect.ValueOf(v)
typ := val.Type()
// 处理指针
if val.Kind() == reflect.Ptr {
val = val.Elem()
typ = val.Type()
}
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
fieldVal := val.Field(i)
tag := field.Tag.Get("validate")
if tag == "" {
continue
}
rules := strings.Split(tag, ",")
for _, rule := range rules {
if err := validateRule(field.Name, fieldVal, rule); err != nil {
errors = append(errors, *err)
}
}
}
return errors
}
func validateRule(name string, val reflect.Value, rule string) *ValidationError {
parts := strings.SplitN(rule, "=", 2)
ruleName := parts[0]
switch ruleName {
case "required":
if val.IsZero() {
return &ValidationError{name, "不能为空"}
}
case "min":
minVal, _ := strconv.Atoi(parts[1])
switch val.Kind() {
case reflect.String:
if len(val.String()) < minVal {
return &ValidationError{name, fmt.Sprintf("长度不能小于%d", minVal)}
}
case reflect.Int, reflect.Int64:
if val.Int() < int64(minVal) {
return &ValidationError{name, fmt.Sprintf("值不能小于%d", minVal)}
}
}
case "max":
maxVal, _ := strconv.Atoi(parts[1])
switch val.Kind() {
case reflect.String:
if len(val.String()) > maxVal {
return &ValidationError{name, fmt.Sprintf("长度不能大于%d", maxVal)}
}
case reflect.Int, reflect.Int64:
if val.Int() > int64(maxVal) {
return &ValidationError{name, fmt.Sprintf("值不能大于%d", maxVal)}
}
}
case "email":
if val.Kind() == reflect.String {
pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
if !regexp.MustCompile(pattern).MatchString(val.String()) {
return &ValidationError{name, "邮箱格式不正确"}
}
}
}
return nil
}
type RegisterForm struct {
Username string `validate:"required,min=3,max=20"`
Password string `validate:"required,min=6"`
Email string `validate:"required,email"`
Age int `validate:"min=0,max=150"`
}
func main() {
form := RegisterForm{
Username: "ab",
Password: "123",
Email: "invalid-email",
Age: -1,
}
errors := Validate(form)
if len(errors) > 0 {
fmt.Println("验证失败:")
for _, err := range errors {
fmt.Printf(" - %s\n", err)
}
}
}
实战案例:简易ORM
package main
import (
"fmt"
"reflect"
"strings"
)
// 生成建表SQL
func CreateTableSQL(v interface{}) string {
t := reflect.TypeOf(v)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
tableName := toSnakeCase(t.Name())
var columns []string
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
colName := field.Tag.Get("db")
if colName == "" {
colName = toSnakeCase(field.Name)
}
colType := goTypeToSQL(field.Type)
// 检查是否主键
if field.Tag.Get("pk") == "true" {
colType += " PRIMARY KEY"
}
columns = append(columns, fmt.Sprintf(" %s %s", colName, colType))
}
return fmt.Sprintf("CREATE TABLE %s (\n%s\n);",
tableName,
strings.Join(columns, ",\n"))
}
func goTypeToSQL(t reflect.Type) string {
switch t.Kind() {
case reflect.String:
return "VARCHAR(255)"
case reflect.Int, reflect.Int64:
return "INTEGER"
case reflect.Float64:
return "REAL"
case reflect.Bool:
return "BOOLEAN"
default:
return "TEXT"
}
}
func toSnakeCase(s string) string {
var result strings.Builder
for i, r := range s {
if i > 0 && r >= 'A' && r <= 'Z' {
result.WriteByte('_')
}
result.WriteRune(r)
}
return strings.ToLower(result.String())
}
// 生成插入SQL
func InsertSQL(v interface{}) (string, []interface{}) {
val := reflect.ValueOf(v)
typ := val.Type()
if val.Kind() == reflect.Ptr {
val = val.Elem()
typ = val.Type()
}
tableName := toSnakeCase(typ.Name())
var columns []string
var placeholders []string
var values []interface{}
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
fieldVal := val.Field(i)
// 跳过主键
if field.Tag.Get("pk") == "true" {
continue
}
colName := field.Tag.Get("db")
if colName == "" {
colName = toSnakeCase(field.Name)
}
columns = append(columns, colName)
placeholders = append(placeholders, "?")
values = append(values, fieldVal.Interface())
}
sql := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)",
tableName,
strings.Join(columns, ", "),
strings.Join(placeholders, ", "))
return sql, values
}
type User struct {
ID int `db:"id" pk:"true"`
Username string `db:"username"`
Email string `db:"email"`
Age int `db:"age"`
}
func main() {
// 生成建表SQL
createSQL := CreateTableSQL(User{})
fmt.Println("建表SQL:")
fmt.Println(createSQL)
// 生成插入SQL
user := User{
Username: "zhangsan",
Email: "zhangsan@example.com",
Age: 25,
}
insertSQL, values := InsertSQL(user)
fmt.Println("\n插入SQL:")
fmt.Println(insertSQL)
fmt.Println("参数:", values)
}
反射性能对比
func BenchmarkDirectCall(b *testing.B) {
calc := Calculator{}
for i := 0; i < b.N; i++ {
calc.Add(1, 2)
}
}
func BenchmarkReflectCall(b *testing.B) {
calc := Calculator{}
v := reflect.ValueOf(calc)
method := v.MethodByName("Add")
args := []reflect.Value{reflect.ValueOf(1), reflect.ValueOf(2)}
for i := 0; i < b.N; i++ {
method.Call(args)
}
}
// 结果:
// BenchmarkDirectCall-8 1000000000 0.3 ns/op
// BenchmarkReflectCall-8 5000000 300.0 ns/op
// 反射慢约1000倍!
何时使用反射
| 场景 | 是否使用反射 | 原因 |
|---|---|---|
| JSON/XML序列化 | ✅ | 需要动态处理结构体 |
| ORM框架 | ✅ | 数据库映射 |
| 依赖注入 | ✅ | 动态创建和注入 |
| 表单验证 | ✅ | 通用验证逻辑 |
| 日常业务代码 | ❌ | 直接调用更好 |
| 性能关键代码 | ❌ | 反射太慢 |
练习
- 使用反射遍历结构体所有字段和值
- 实现一个通用的深拷贝函数
- 实现一个结构体转Map的函数
参考答案
// 1. 遍历结构体
func PrintStruct(v interface{}) {
val := reflect.ValueOf(v)
typ := val.Type()
if val.Kind() == reflect.Ptr {
val = val.Elem()
typ = val.Type()
}
fmt.Printf("类型: %s\n", typ.Name())
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
value := val.Field(i)
fmt.Printf(" %s: %v (%s)\n", field.Name, value.Interface(), field.Type)
}
}
// 2. 深拷贝
func DeepCopy(src interface{}) interface{} {
srcVal := reflect.ValueOf(src)
if srcVal.Kind() == reflect.Ptr {
srcVal = srcVal.Elem()
}
dst := reflect.New(srcVal.Type()).Elem()
copyValue(dst, srcVal)
return dst.Interface()
}
func copyValue(dst, src reflect.Value) {
switch src.Kind() {
case reflect.Struct:
for i := 0; i < src.NumField(); i++ {
if dst.Field(i).CanSet() {
copyValue(dst.Field(i), src.Field(i))
}
}
case reflect.Slice:
dst.Set(reflect.MakeSlice(src.Type(), src.Len(), src.Cap()))
for i := 0; i < src.Len(); i++ {
copyValue(dst.Index(i), src.Index(i))
}
case reflect.Map:
dst.Set(reflect.MakeMap(src.Type()))
for _, key := range src.MapKeys() {
dst.SetMapIndex(key, src.MapIndex(key))
}
default:
dst.Set(src)
}
}
// 3. 结构体转Map
func StructToMap(v interface{}) map[string]interface{} {
result := make(map[string]interface{})
val := reflect.ValueOf(v)
typ := val.Type()
if val.Kind() == reflect.Ptr {
val = val.Elem()
typ = val.Type()
}
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
value := val.Field(i)
if !value.CanInterface() {
continue
}
name := field.Tag.Get("json")
if name == "" || name == "-" {
name = field.Name
}
result[name] = value.Interface()
}
return result
}
反射很强大,但记住:能不用就不用。下一节学习泛型!
