Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5Mobile wallpaper 6
3108 字
16 分钟
反射
2025-07-15

反射的引入#

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

PixPin_2025-07-15_18-23-14PixPin_2025-07-15_18-23-29

对基本数据类型反射#

【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)
}

PixPin_2025-07-15_22-05-28 这段代码演示了 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) // 输出:int
fmt.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) // 输出:100
fmt.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 + 30
fmt.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 uint

Kind代表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() Value

Elem返回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 int
    Name string
    }
  • 框架需要自动生成 SQL:

    INSERT INTO users (id, name) VALUES (?, ?)
  • 就需要用反射获取字段名、字段值。


3️⃣ 写通用工具 / 库#

比如写一个:

  • 日志工具:能打印任意结构体内容
  • 验证工具:校验结构体字段
  • Web 框架:把 HTTP 请求参数自动绑定到结构体

这些都需要对「未知类型」做操作,只能在运行时用反射。


4️⃣ RPC 框架 / 接口注册#

比如写一个远程调用框架:

  • 用户写了:

    func Add(a, b int) int { return a+b }
  • 框架在启动时,把这个函数注册到一个 map

  • 收到网络请求时,根据函数名动态找到方法,用反射调用


🚀 总结一句话:#

反射的作用是:在不知道类型或字段/方法名的情况下,在运行时对对象进行统一、动态的操作。


⚠️ 注意:#

  • 反射灵活,但有缺点:
    • 性能慢(需要做很多运行时检查)
    • 写起来复杂
    • 失去类型安全
  • 所以:正常业务逻辑尽量不用反射,只在做框架、通用库、工具时用。

反射
https://mikufun.dpdns.org/posts/反射/
作者
Roxy-DD
发布于
2025-07-15
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00