反射的引入
【1】反射可以做什么? 1)反射可以在运行时动态获取变量的各种信息,比如变量的类型,类别等信息 2)如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法) 3)通过反射,可以修改变量的值,可以调用关联的方法。 4)使用反射,需要import(“reflect”) 【2】反射相关的函数 1)reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型 2)reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型(reflect.Value是一个结构体类型),通过reflect.Value,可以获取到关于该变量的很多信息。


对基本数据类型反射
【1】反射相关的函数 1)reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型 2)reflectValueOf变量名),获取变量的值,返回reflect.Value类型(reflect.Value是一个结构体类型),通过reflect.Value,可以获取到关于该变量的很 多信息。 【2】代码:
package main
import ( "fmt" "reflect")
//利用一个函数,函数的参数定义为空接口:func testReflect(i interface{}) {//空接口没有任何方法,所以可以理解为所有类型都实现了空接口,也可以理解为我们可以把任何一个变量赋给空接口。 //1.调用TypeOf函数,返回reflect.Type类型数据: reType := reflect.TypeOf(i) fmt.Println("reType:",reType) fmt.Printf("reType的具体类型是:%T",reType) //2.调用ValueOf函数,返回reflect.Value类型数据: reValue := reflect.ValueOf(i) fmt.Println("reValue",reValue) fmt.Printf("reValue的具体类型是:%T",reValue) //num1 := 100 //如果真想获取reValue的数值,要调用Int方法:返回v持有的有符号整数 num2 := 80 + reValue.Int() fmt.Println(num2)
//reValue转成空接口: i2 := reValue.Interface() //类型断言: n := i2.(int) n2 := n + 30 fmt.Println(n2)
}func main() { //对基本数据类型进行反射: //定义一个基本数据类型: var num int = 100 testReflect(num)
}
这段代码演示了 Go 中反射(reflect)的核心用法,并且还结合了类型断言。
我来帮你详细讲解,包括:
- 为什么要用 reflect
- 每一步干了什么
- interface{} 的作用
- 最后断言部分为什么需要、怎么做
✅ 先看 big picture:做了什么?
- 定义了一个函数:testReflect(i interface{})
- 接收任何类型的值(因为参数是 interface{},即空接口)
- 用反射拿到:
- 类型信息(reflect.Type)
- 值信息(reflect.Value)
- 通过 reValue.Int() 获取实际的 int 数值
- 最后通过 reValue.Interface() 转回 interface{},再用类型断言拿到真正的 int
🧩 逐行讲解
🌱 空接口:万能参数
func testReflect(i interface{})- 在 Go 里,空接口 interface{} 表示任意类型都可以传进来
- 所以可以对任意值进行反射
1️⃣ 获取类型
reType := reflect.TypeOf(i)fmt.Println("reType:", reType) // 输出:intfmt.Printf("reType的具体类型是:%T", reType) // 输出:*reflect.rtype(或 reflect.Type)- reflect.TypeOf(i) 返回一个 reflect.Type 对象
- 可以用它看变量的类型,比如 int、string、struct 等
2️⃣ 获取值
reValue := reflect.ValueOf(i)fmt.Println("reValue", reValue) // 输出:100fmt.Printf("reValue的具体类型是:%T", reValue) // reflect.Value- reflect.ValueOf(i) 返回 reflect.Value
- reflect.Value 可以进一步操作:获取值、修改值(如果可修改)
3️⃣ 真正获取数值
num2 := 80 + reValue.Int()fmt.Println(num2)- 因为 reValue 是 int 类型的值,想得到它的数值,需要用 reValue.Int() 方法
- Int() 返回 int64(注意是 int64)
- 可以做加法运算
4️⃣ 转回 interface{} 并断言
i2 := reValue.Interface()n := i2.(int) // 类型断言n2 := n + 30fmt.Println(n2)这里很关键:
- reValue.Interface() 返回一个空接口(interface{})
- 想要拿到真正的 int,需要告诉编译器:我确定这个 interface{} 里装的就是 int
- 所以用 类型断言:i2.(int)
类型断言语法:
v, ok := i2.(int)- 如果是 int 类型:ok == true
- 如果不是 int 类型:ok == false,v 是默认值(0)
这里直接用:
n := i2.(int)- 如果不是 int,会 panic(运行时报错)
✅ 为什么需要类型断言?
因为:
- 空接口 interface{} 自身什么都不能做,只能装值
- 要想用里面的值(比如做加法、调用方法),必须先确定具体类型
类型断言:让编译器/运行时知道:我确定这是 int,然后就能当作 int 用。
🔧 总结下流程:
| 步骤 | 作用 |
|---|---|
| reflect.TypeOf(i) | 得到类型 |
| reflect.ValueOf(i) | 得到值 |
| reValue.Int() | 从 reflect.Value 得到真实的 int 数值 |
| reValue.Interface() | 转成 interface{} |
| .(int) | 类型断言,拿到具体类型的值 |
✏ 为什么要 Interface() 再断言?
- reflect.Value 是反射的容器,如果要拿回「原始值」就要用 .Interface()
- Interface() 返回值是 interface{}
- 然后类型断言拿到真正的值,继续用
🧠 一图总结:
值 num=100 --> 空接口 i=interface{} --> reflect.Value reValue --> reValue.Int() \ --> reValue.Interface()=interface{} --> 类型断言 .(int) --> n=100🚀 一句话总结:
Go 的反射:先拿到 Type 和 Value,如果要用 Value 的真实值:
- 用 reValue.Int()/String() 等直接取
- 或者 reValue.Interface() 转回 interface{},再类型断言拿到原始类型
⚡ 反射灵活,但慢,不建议频繁用。
对结构体类型反射
【1】反射相关的函数 1)reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型 2)reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型(reflect.Value是一个结构体类型),通过reflect.Value,可以获取到关于该变量的很多信息。 【2】代码:
package main
import ( "fmt" "reflect")
//利用一个函数,函数的参数定义为空接口:func testReflect(i interface{}) {//空接口没有任何方法,所以可以理解为所有类型都实现了空接口,也可以理解为我们可以把任何一个变量赋给空接口。 //1.调用TypeOf函数,返回reflect.Type类型数据: reType := reflect.TypeOf(i) fmt.Println("reType:",reType) fmt.Printf("reType的具体类型是:%T",reType) //2.调用ValueOf函数,返回reflect.Value类型数据: reValue := reflect.ValueOf(i) fmt.Println("reValue",reValue) fmt.Printf("reValue的具体类型是:%T",reValue)
//reValue转成空接口: i2 := reValue.Interface() //类型断言: n ,flag := i2.(Student) if flag == true {//断言成功 fmt.Printf("学生的名字是:%v,学生的年龄是%v",n.Name,n.Age) }
}
type Student struct{ Name string Age int
}
func main() { //对结构体类型进行反射: //定义结构体具体的类型: stu := Student{ Name: "丽丽", Age: 18, } testReflect(stu)
}获取变量的类别
【1】获取变量的类别:两种方式: (1) reflect.Type.Kind0 (2)reflect.Value.Kind0 【2】Kind的值是常量值:
type Kind
type Kind uintKind代表Type类型值表示的具体分类。零值表示非法分类。
const ( Invalid Kind = iota Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 Uintptr Float32 Float64 Complex64 Complex128 Array Chan Func Interface Map Ptr Slice String Struct UnsafePointer)【3】代码:
package main
import ( "fmt" "reflect")
//利用一个函数,函数的参数定义为空接口:func testReflect(i interface{}) {//空接口没有任何方法,所以可以理解为所有类型都实现了空接口,也可以理解为我们可以把任何一个变量赋给空接口。 //1.调用TypeOf函数,返回reflect.Type类型数据: reType := reflect.TypeOf(i)
//2.调用ValueOf函数,返回reflect.Value类型数据: reValue := reflect.ValueOf(i)
//获取变量的类别: //(1)reType.kind() k1:=reType.Kind() fmt.Println(k1)
//(2)reValue.kind() k2:=reValue.Kind() fmt.Println(k2)
//获取变量的类型: //reValue转成空接口: i2 := reValue.Interface() //类型断言: n ,flag := i2.(Student) if flag == true {//断言成功 fmt.Printf("结构体的类型是:%T",n) }}
type Student struct{ Name string Age int
}
func main() { //对结构体类型进行反射: //定义结构体具体的类型: stu := Student{ Name: "丽丽", Age: 18, } testReflect(stu)
}通过反射修改变量
【1】修改基本数据类型的值:
package main
import ( // "fmt" "fmt" "reflect")
//利用一个函数,函数的参数定义为空接口:func testReflect(i interface{}) {//空接口没有任何方法,所以可以理解为所有类型都实现了空接口,也可以理解为我们可以把任何一个变量赋给空接口。 reValue := reflect.ValueOf(i) //通过SetInt()来改变值: reValue.Elem().SetInt(40)
}func main() { //对基本数据类型进行反射: //定义一个基本数据类型: var num int = 100 testReflect(&num)//传入指针地址 fmt.Println(num)}func (Value) Elem
func (v Value) Elem() ValueElem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。
通过反射操作结构体的属性和方法
【1】代码:(熟知API)
package main
import ( // "fmt" "fmt" "reflect")//定义一个结构体:type Student struct { Name string Age int}
//给结构体绑定方法:func (s Student)CPrint(){ fmt.Println("调用了CPrint()方法") fmt.Println("学生的名字是:",s.Name)}
func (s Student)AGetSum(n1,n2 int) int { fmt.Println("调用了AGetSum()方法") return n1+ n2}
func (s Student)BSet(name string,age int) { fmt.Println("调用了BSet()方法") s.Name = name s.Age = age}
//定义函数操作结构体进行反射操作:func TestStudentStruct(a interface{}) { //a转成reflect.Value类型: val := reflect.ValueOf(a) fmt.Println(val)
//通过Ireflect.Value类型操作结构体内部的字段: n1 := val.NumField() fmt.Println(n1) //遍历-获取具体的字段: for i:=0;i<n1;i++{ fmt.Printf("第%d个字段的值是:%v\n",i,val.Field(i)) }
//通过reflect.Value类型操作结构体内部的方法: n2 := val.NumMethod() fmt.Println(n2)
//调用CPrint()方法: //调用方法,方法的首字母必须大写才能用=有对应的反射的访问权限 //方法的顺序按照ASCII的顺序排列的,a,b,c,,,,,索引:
val.Method(2).Call(nil)
//调用AGetSum方法: //定义Value的切片: var params []reflect.Value params = append(params, reflect.ValueOf(10)) params = append(params, reflect.ValueOf(20)) result := val.Method(0).Call(params) fmt.Println("AGetSum方法的返回值为:",result[0].Int())}func main() { s:= Student{ Name: "丽丽", Age: 18, } //调用TestStudentStruct: TestStudentStruct(s)}通过反射修改变量
【1】代码:
package main
import ( // "fmt" "fmt" "reflect")//定义一个结构体:type Student struct { Name string Age int}
//给结构体绑定方法:func (s Student)CPrint(){ fmt.Println("调用了CPrint()方法") fmt.Println("学生的名字是:",s.Name)}
func (s Student)AGetSum(n1,n2 int) int { fmt.Println("调用了AGetSum()方法") return n1+ n2}
func (s Student)BSet(name string,age int) { fmt.Println("调用了BSet()方法") s.Name = name s.Age = age}
//定义函数操作结构体进行反射操作:func TestStudentStruct(a interface{}) { //a转成reflect.Value类型: val := reflect.ValueOf(a) fmt.Println(val)
n := val.Elem().NumField() fmt.Println(n)
//修改字段的值: val.Elem().Field(0).SetString("张三")
}func main() { s:= Student{ Name: "丽丽", Age: 18, } //调用TestStudentStruct: TestStudentStruct(&s) fmt.Println(s)}✅ 反射到底有什么用? ✅ 为什么不用正常写法而要用反射? ✅ 实际项目里什么情况下才用?
我帮你从原理、作用到实际应用详细讲解:
🧪 📦 反射的本质:
反射就是在运行时(runtime)获取、修改对象的结构和行为:
- 获取:查看一个变量的类型、字段、方法
- 修改:修改字段值
- 调用:动态调用方法
而不是在编译期就写死。
🎯 为什么不用正常写法,而用反射?
正常写:
s.Name = "张三"fmt.Println(s.AGetSum(1, 2))优点:
- 简洁
- 类型安全(编译时检查)
反射写:
- 不知道变量具体类型,只知道它是 interface{}
- 要到运行时再确定字段名、方法名、参数个数
- 可以写「通用」代码,对很多不同类型对象进行同样的处理
🧰 实际项目中常见用途:
下面举几个经典例子:
1️⃣ 通用序列化 / 反序列化
比如:
- 把结构体转 JSON
- 从 JSON 转结构体
你需要动态访问结构体字段:
for i := 0; i < val.NumField(); i++ { fieldName := val.Type().Field(i).Name fieldValue := val.Field(i).Interface() // 组装成 map/json}Go 自带的 encoding/json 包就大量用反射实现。
2️⃣ ORM 框架(对象关系映射)
比如 GORM:
-
用户定义结构体:
type User struct {ID intName string} -
框架需要自动生成 SQL:
INSERT INTO users (id, name) VALUES (?, ?) -
就需要用反射获取字段名、字段值。
3️⃣ 写通用工具 / 库
比如写一个:
- 日志工具:能打印任意结构体内容
- 验证工具:校验结构体字段
- Web 框架:把 HTTP 请求参数自动绑定到结构体
这些都需要对「未知类型」做操作,只能在运行时用反射。
4️⃣ RPC 框架 / 接口注册
比如写一个远程调用框架:
-
用户写了:
func Add(a, b int) int { return a+b } -
框架在启动时,把这个函数注册到一个 map
-
收到网络请求时,根据函数名动态找到方法,用反射调用
🚀 总结一句话:
反射的作用是:在不知道类型或字段/方法名的情况下,在运行时对对象进行统一、动态的操作。
⚠️ 注意:
- 反射灵活,但有缺点:
- 性能慢(需要做很多运行时检查)
- 写起来复杂
- 失去类型安全
- 所以:正常业务逻辑尽量不用反射,只在做框架、通用库、工具时用。
部分信息可能已经过时









