引子
// 由于反射是基于类型系统 (type system) 的,所以先简单了解下类型系统
type MyInt int
README
var i int
var j MyInt
// 上面的 i 是 int 类型,j 是 MyInt 类型,i 和 j 是不同的静态类型,尽管他们都有相同的相关类型(这里是int)
// 它们不能互相赋值,除非通过强制的类型转换
/*
一种非常重要的类型分类是接口类型,接口代表方法的集合,只要一个值实现了接口定义的方法,那么这个接口就可以存储这个具体的值
一个著名的例子就是 io 包中的 Reader 和 Writer
*/
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
/*
任何是实现了 Read 或 Write 方法的签名的类型就是实现了 io.Reader 或 io.Writer
也就是说一个 io.Reader 的变量可以持有任何实现了 Read 方法的值
*/
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// ....
/*
我们要非常清楚的知道不管 r 持有了哪种具体的值,r 的类型永远都是 io.Reader
一个非常重要的的例子就是空接口 interface{},它代表一个空的方法集合并且满足任何值,只要这个值有零或多个方法
简单来说,interface 值存储了一个赋给它的具体值和对这个值类型的描述
*/
var r io.Reader
tty,err := os.OpenFile("/dev/tty",os.O_RDWR,0)
if err != nil {
return nil,err
}
r = tty
// 在上面这个具体例子中的 r 包含了一个 (value, type) 对,具体就是 (tty, *os.File)
// *os.File 实现了 Read 等很多方法,但是 io.Reader 的接口只允许访问 Read 方法,所以我们还可以这样做:
var w io.Writer
w = r.(io.Writer)
// 通过类型断言 "type assertion",因为 r 照样实现了 io.Writer,所以我们可以将 r 赋值给 w
https://pkg.go.dev/reflect
https://go.dev/blog/laws-of-reflection
# 理解变量的内在机制
# ● 类型信息(元信息): 是预先定义好的、静态的
# ● 值信息: 是在程序运行过程中动态变化的
# 反射和空接口 ...
# 空接口就像是能接受任何东西的容器,虽然可以通过类型断言来判断空接口变量中保存的是什么类型,但这只是比较基础的方法
# 如果需要获取变量的类型信息和值信息就得使用反射机制(反射就是动态的获取变量的类型信息和值信息的机制)
# 通过 reflect 包能获取 interface{} 变量的具体类型以及值信息(分析空接口里面的信息)
# Golang 中,反射是指程序在运行过程中分析和操作类型、变量和函数等程序结构的能力
# 通过反射可以在运行时动态的获取和修改变量的类型、值和方法等信息,而不需要在编译时就确定这些信息(是元编程的一种形式)
# 使用反射可以使代码更加灵活和通用,但是由于反射是在运行时进行的,所以它的性能相对较低
# 反射是语言非常重要的特性,但除非真的有必要,否则应避免或小心使用反射
# 在计算机科学领域,反射是指一类应用,它们能够自描述和自控制
# 也就是说这类应用通过某种机制实现对自己行为的描述 self-representation 和监测 examination
# 并根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义
# ------------------------------------------------------------------------------------------
# 总结:
# 本质上来说,反射就是一种检查接口变量的类型和值的机制 ...
# ● 从 interface{} 变量可以反射出反射对象
# ● 从反射对象可以获取 interface{} 变量
# ● 要修改反射对象,其值必须可设置
# 场景:
# ● 动态的创建对象、获取和修改对象的字段值、动态的调用对象的方法
# ● 序列化与反序列化,如 json、protobuf 等各种数据协议
# ● 各种数据库的 ORM 如 gorm、sqlx 等数据库中间件
# ● 配置文件解析相关的库,如 YAML、INI、...
# ------------------------------------------------------------------------------------------
func reflect.TypeOf(i any) reflect.Type
# 返回的是 reflect.Type 接口,表示任意变量的具体类型信息
# 该接口中定义了一些有趣的方法,例如 MethodByName 获取当前类型对应方法的引用、Implements 判断当前类型是否实现了某接口
func reflect.ValueOf(i any) reflect.Value
# 返回的是 reflect.Value 结构体,表示接口的具体值(数据的运行时表示)
# 这个结构体没有对外暴露的字段,但是提供了获取和写入数据的方法
# 反射包中的所有方法基本都是围绕着 Type 和 Value 这两个类型设计的
# 我们通过 TypeOf 和 ValueOf 可以将普通的变量转换成反射包中提供的 Type 和 Value
# 如果我们知道了一个变量的类型和值,那么就意味着知道了这个变量的全部信息
# 然后就可以使用反射包中的方法对它们进行复杂的操作了
reflect.TypeOf()
var a interface{} = 123.456
// 获取变量的类型信息
func main() {
// 返回的是 reflect.Type 接口,该接口中定义了若干方法,可通过它们获取关于类型的所有信息
t := reflect.TypeOf(a) // func reflect.TypeOf(i any) reflect.Type
// 相应值的默认格式: %v
fmt.Printf("%v\n", t) // float64
// 通过该接口的 Kind 方法返回表示底层具体数据类型的常量(返回类型的类型值)
switch t.Kind() {
case reflect.Float64:
fmt.Printf("arg is Float64\n") // arg is Float64
case reflect.String:
fmt.Printf("arg is string\n")
}
}
reflect.ValueOf()
// 通过反射可以在变量、interface{}、reflect.Value 三者之间相互转换 ...
// ------------------------------------------------------------------------------------------
var a string = "hello, world"
func main() {
// 返回的是 reflect.Value 结构体类型,包含类型信息以及实际值
v := reflect.ValueOf(a) // func reflect.ValueOf(i any) reflect.Value
// 因为值是动态的,所以不仅可以获取这个变量的类型,还能获取其中存储的值,利用 v.Int()、v.String()、... 就能取得
// 也可以用 reflect.Value 轻松得到 reflect.Type
// 例如用 v.Type() 获取该变量的类型,这与上例中 reflect.TypeOf() 返回的接口对象的 Kind() 方法的结果相同
fmt.Println("type:", v.Type()) // type: string
k := v.Kind()
switch k {
case reflect.Int64:
fmt.Printf("a is Int64, store value is: %d\n", v.Int())
case reflect.String:
fmt.Printf("a is String, store value is: %s\n", v.String()) // a is String, store value is: hello, world
}
}
// 这里存在一个问题:
// 若传进去一个类型,使用了错误的解析,则会在运行时报错,例如将 string 类型强行的 v.Int()
reflect.Kind
// reflect.Kind 类型虽然看起来和 reflect.Type 相似,但两者有很大差异
// ------------------------------------------------------------------------------------------
type order struct {
ordId int
customerId int
}
var o = order{
ordId: 456,
customerId: 56,
}
func main() {
// 返回的是 reflect.Type 接口,该接口中定义了若干方法,可通过它们获取关于类型的所有信息
t := reflect.TypeOf(o) // func reflect.TypeOf(i any) reflect.Type
// 通过接口 Kind 方法返回表示底层具体数据类型的常量(返回类型的类型值)
k := t.Kind() // func (reflect.Type).Kind() reflect.Kind
fmt.Println(t) // main.order 即 reflect.Type 代表实际的类型 main.order
fmt.Println(k) // struct 即 reflect.Kind 代表类型的分类
修改变量的值
// 既然值类型是动态的,能取到保存的值同样也可以设置值
// 在反射中提供了很多 set 方法 SetFloat、SetInt()、SetString()、... 可以帮助设置类型的值
// 下例将 x 的值设为 6.28 会报错
func main() {
var x float64 = 3.14
v := reflect.ValueOf(x)
v.SetFloat(6.28)
fmt.Printf("After Set Value is %f", x)
}
// panic: reflect: reflect.Value.SetFloat using unaddressable value
// 报错结果说明是不可设置的,这是因为 x 是值类型,而值类型传递的是副本
// 因为 v := reflect.ValueOf(x) 通过传递 x 的拷贝创建了 v,所以 v 的改变并不能更改原始的 x,所以必须传递 x 的地址:
func main() {
var x float64 = 3.14
v := reflect.ValueOf(&x)
// fmt.Printf("type of v is %v", v.Type()) // type of v is *float64
v.SetFloat(6.28)
fmt.Printf("After Set Value is %f", x)
}
// 依然会报错! 这是因为 &x 是地址了,所以它的类型就变了,通过上述注释部分看到变成了 *float64
// 正常的赋值,如果是地址的话,如下例中对 *y 赋值(这里 * 的意思就是往这个地址里面赋值)
var y *float64 = new(float64)
*y = 10.12
fmt.Printf("y = %v", *y)
// 同样,在反射里面也可以取地址,但是需要通过 Elem() 取地址
func main() {
var x float64 = 3.14 // x 是值类型
v := reflect.ValueOf(&x) // 需要使用指针(避免传递变量的副本)
fmt.Printf("type of v is %v\n", v.Type()) // type of v is *float64
v.Elem().SetFloat(6.28) // 获取指针指向的变量,然后再赋值
fmt.Printf("After set x is %v", x) // After set x is 6.28
}
// ------------------------------------------------------------------------------------------ 修改结构体中字段的值
type Student struct {
Name string
Sex int
Age int
Score float32
}
func main() {
s := Student{
Name: "BigOrange",
Sex: 1,
Age: 10,
Score: 80.1,
}
fmt.Printf("Name:%v, Sex:%v,Age:%v,Score:%v \n", s.Name, s.Sex, s.Age, s.Score)
// Name:BigOrange, Sex:1,Age:10,Score:80.1
v := reflect.ValueOf(&s) // 这里传的是地址
v.Elem().Field(0).SetString("ChangeName") // 对结构体中的字段进行赋值操作
v.Elem().FieldByName("Score").SetFloat(99.9) // ...
fmt.Printf("Name:%v, Sex:%v,Age:%v,Score:%v \n", s.Name, s.Sex, s.Age, s.Score)
// Name:ChangeName, Sex:1,Age:10,Score:99.9
}
获取结构体中字段的信息
// 判断一个变量是不是结构体并获取结构体中的字段
// 通过 NumField() 获取结构体中的字段数量(从而实现遍历)
// 通过 Field() 获取结构体中字段的信息
type Student struct {
Name string
Sex int
Age int
Score float32
}
func main() {
var s Student = Student{
Name: "BigOrange",
Sex: 1,
Age: 10,
Score: 80.1,
}
v := reflect.ValueOf(s) // func reflect.ValueOf(i any) reflect.Value
t := v.Type() // func (reflect.Value).Type() reflect.Type
// 返回表示其底层数据类型的一个常量值
kind := t.Kind() // func (reflect.Type).Kind() reflect.Kind
switch kind {
case reflect.Int64: // ...
fmt.Printf("s is int64\n")
case reflect.Float32: // ...
fmt.Printf("s is int64\n")
case reflect.Struct: // 如果是结构体类型则遍历其中所有的字段
fmt.Printf("s is struct\n")
fmt.Printf("field number of is %d\n", v.NumField())
for i := 0; i < v.NumField(); i++ { // 获取结构体中字段的数量
// 通过 Field() 取得按下标索引的字段信息,返回 reflect.Value 结构体类型的值
field := v.Field(i) // func (reflect.Value).Field(i int) reflect.Value
fmt.Printf("name:%s type:%v value: %v\n",
t.Field(i).Name, // 字段名称
field.Type().Kind(), // 字段类型
field.Interface(), // 字段的值,也可以用 reflect.Value.Interface().(type) 的方式还原成最原始的值~
)
}
default:
fmt.Printf("default\n")
}
}
/*
s is struct
field number of is 4
name:Name type:string value: BigOrange
name:Sex type:int value: 1
name:Age type:int value: 10
name:Score type:float32 value: 80.1
--------------------------------------------------------------------------------------------- Tips
打印字段名时使用的是 t.Field(i).Name,因为 Name 是静态的,所以属于类型的信息
打印值的时候,这里将 field.Interface() 实际上相当于 ValueOf 的反操作
可参考这篇文章 https://www.cnblogs.com/baicaiyoudu/archive/2016/09/25/5905766.html,所以才能把值打印出来
如果 Student 中的 Name 字段变为 name(私有)则会报错(不能反射私有变量)
"panic: reflect.Value.Interface: cannot return value obtained from unexported field or method"
*/
获取结构体中方法的信息
type Student struct {
Name string
Sex int
Age int
Score float32
}
func (s *Student) SetName(name string) {
fmt.Printf("有参数方法,通过反射进行调用")
s.Name = name
}
func (s *Student) PrintStudent() {
fmt.Printf("无参数方法,通过反射进行调用")
}
// ------------------------------------------------------------------------------------------
func main() {
s := Student{
Name: "BigOrange",
Sex: 1,
Age: 10,
Score: 80.1,
}
v := reflect.ValueOf(&s)
t := v.Type()
fmt.Printf("struct student have %d methods\n", t.NumMethod())
// 获得结构体的方法数量,然后遍历并通过 Method() 获取结构体方法的具体信息
for i := 0; i < t.NumMethod(); i++ {
method := t.Method(i)
fmt.Printf("struct %d method, name:%s type: %v\n", i, method.Name, method.Type)
}
// 调用方法
if method.Name == "SetName" {
method.Func.Call([]reflect.Value{reflect.ValueOf(&s), reflect.ValueOf("1")})
}
}
/*
struct student have 2 methods
struct 0 method, name:PrintStudent type: func(*main.Student)
struct 1 method, name:SetName type: func(*main.Student, string)
有参数方法,通过反射进行调用
从输出中可以看到能够获取方法的名称及其签名,并且这个方法的输出顺序是按字母排列的
从输出中可以看到一个有趣的现象: 结构体的方法其实也是通过函数实现的
例如结构体方法 func(s Student) SetName(name string) 反射之后的结果是 func(main.Student, string),因此实际上是把 Student 当参数了 ...
*/
获取结构体中标签的信息
// 有时在类型上定义一些 tag,例如使用 json 和数据库时 ...
// Field() 方法返回的 StructField 结构体对象中保存着 Tag 信息,并且可以通过其 Get() 方法获取 Tag 信息
type Student struct {
Name string `json:"jsName" db:"dbName"`
}
func main() {
s := Student{
Name: "BigOrange",
}
v := reflect.ValueOf(&s) // func reflect.ValueOf(i any) reflect.Value
t := v.Type() // func (reflect.Value).Type() reflect.Type
field0 := t.Elem().Field(0) // func (reflect.Type).Field(i int) reflect.StructField
fmt.Printf("tag json=%s\n", field0.Tag.Get("json")) // tag json=jsName
fmt.Printf("tag db=%s\n", field0.Tag.Get("db")) // tag db=dbName
}
// ------------------------------------------------------------------------------------------- 获取结构体的 tag 信息
type resume struct {
Name string `json:"name" doc:"wangyu"`
}
func findDocOnTag(stru interface{}) map[string]string {
t := reflect.TypeOf(stru).Elem()
doc := make(map[string]string)
for i := 0; i < t.NumField(); i++ {
doc[t.Field(i).Tag.Get("json")] = t.Field(i).Tag.Get("doc")
}
return doc
}
func main() {
var stru resume
doc := findDocOnTag(&stru)
fmt.Printf("name字段为: %s\n", doc["name"]) // name字段为: wangyu
}
标签:reflect,示例,type,fmt,Golang,func,Printf,Type
From: https://www.cnblogs.com/MoonGlow/p/17553111.html