Golang 反射

Go 语言中的反射(Reflection)是一种在运行时动态检查(编译阶段不知道类型)、修改程序自身结构和行为的能力,主要通过 reflect 包实现。

一、变量类型

1. 静态类型

编译时就能确定的类型,称为静态类型。

2. 动态类型

运行时才知道的类型,称为动态类型(例如,空接口)。

二、核心类型与函数

Go 提供了一组方法来获取 interface 的值和类型。

1. reflect.Type

表示变量的类型信息(如 int、string 或自定义结构体),通过 reflect.TypeOf() 获取。

2. reflect.Value

存储变量的实际值,通过 reflect.ValueOf() 获取。

3. reflect.Kind

描述变量的底层类型(如 reflect.Intreflect.Struct),通过 v.Kind() 获取。

与 Type 的区别:Kind 仅做类型分类,不包含完整的类型信息。

三、实现原理

每个 interface 变量都对应一个 pair,记录了实际变量和类型:

(value, type)

1. 组成部分

  • value:实际变量的值
  • type:实际变量的类型

2. 存储结构

一个 interface 类型的变量包含两个指针,一个指向值的类型,另一个指向实际值。

四、应用场景

1. 通用工具开发

(1)序列化/反序列化

JSON、XML 等格式转换时,动态解析结构体字段和标签。

(2)ORM 框架

数据库查询结果映射到结构体。

2. 依赖注入与插件系统

动态创建对象实例、调用方法(如 reflect.New())。

3. 测试框架

泛化测试函数,支持多类型参数。

五、反射三大定律

1. 第一定律:反射可以将 interface 类型转换成反射对象

package main

import (
	"fmt"
	"reflect"
)

func test01() {
	var x float64 = 3.4
	t := reflect.TypeOf(x) // 获取类型信息
	v := reflect.ValueOf(x) // 获取值信息
	fmt.Printf("type: %v, value: %v\n", t, v)
	fmt.Printf("type name: %v, value: %v\n", t.Name(), v.Float())
}

func main() {
	test01() // 输出:type: float64, value: 3.4;type name: float64, value: 3.4
}

2. 第二定律:反射可以将反射对象还原成 interface 类型

package main

import (
	"fmt"
	"reflect"
)

func test02() {
	var a interface{}
	a = 100

	v := reflect.ValueOf(a)
	b := v.Interface() // 反射对象还原为 interface

	if a == b {
		fmt.Println("a == b") // 输出:a == b
	}
}

func main() {
	test02()
}

3. 第三定律:反射对象可修改,value 值必须是可设置的

  • 若要修改反射对象的值,需确保其可寻址(即指向原变量的指针),通过 Elem() 方法获取指针指向的元素。
package main

import (
	"fmt"
	"reflect"
)

// 错误示例:值不可设置
func test03Error() {
	var x float64 = 3.4
	v := reflect.ValueOf(x) // 传递的是 x 的副本,不可寻址
	// v.SetFloat(7.0) // 运行会 panic:reflect: reflect.Value.SetFloat using unaddressable value
}

// 正确示例:通过指针获取可设置的值
func test03Correct() {
	var x float64 = 3.4
	v := reflect.ValueOf(&x) // 传递指针
	if v.CanSet() {
		fmt.Println("可设置")
	} else {
		fmt.Println("不可设置,需通过 Elem() 获取元素") // 输出此句
		v.Elem().SetFloat(7.0) // 通过 Elem() 获取指针指向的元素,再修改
		fmt.Println("修改后 x 的值:", x) // 输出:修改后 x 的值: 7
	}
}

func main() {
	test03Error()
	test03Correct()
}

六、局限性

1. 性能开销大

反射操作比静态代码慢 10 - 100 倍,频繁调用时需谨慎(例如,为提高数据库查询效率,可使用原生 SQL 代替 ORM)。

2. 破坏编译时类型检查

Go 作为静态语言,编译器可提前发现类型错误,但无法检查反射代码的类型问题。

3. 代码可读性差

反射代码可读性较低,维护成本高。