反射用途
反射在许多方面都非常有用,比如:
动态编程: 通过反射,你可以动态地创建对象,调用方法,甚至构建全新的类型。
框架与库开发: 很多流行的Go框架,如Gin、Beego等,都在内部使用反射来实现灵活和高度可定制的功能。
元编程: 你可以写出可以自我分析和自我修改的代码,这在配置管理、依赖注入等场景中尤为有用。
作者:techlead_krischang
链接:https://juejin.cn/post/7291094611484966970
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
反射与类型系统
反射紧密地与类型系统联系在一起。在静态类型语言(例如Go)中,每一个变量都有预先定义的类型,这些类型在编译期就确定。但反射允许你在运行时去查询和改变这些类型。
// 代码示例:查询变量的类型和值
import "reflect"
func main() {
var x int = 42
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
fmt.Println("Type:", t)
fmt.Println("Value:", v)
}
// 输出: Type: int
// 输出: Value: 42
反射与接口
在Go中,接口(interface)和反射是紧密相关的。事实上,你可以认为接口是实现反射的“入口”。当你将一个具体类型的变量赋给一个接口变量时,这个接口变量内部存储了这个具体变量的类型信息和数据。
// 代码示例:接口与反射
type Any interface{}
func inspect(a Any) {
t := reflect.TypeOf(a)
v := reflect.ValueOf(a)
fmt.Println("Type:", t, "Value:", v)
}
func main() {
var x int = 10
var y float64 = 20.0
inspect(x) // Type: int Value: 10
inspect(y) // Type: float64 Value: 20
}
package main
import (
"fmt"
"reflect"
)
type Human struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
var human Human = Human{
Name: "meowrain",
Age: 20,
}
tp := reflect.TypeOf(human)
vp := reflect.ValueOf(human)
fmt.Println(tp)
fmt.Println(vp)
}
结果:
main.Human
{meowrain 20}
反射的分类
反射在Go中主要有两个方向:
类型反射(Type Reflection): 主要关注于程序运行时获取变量的类型信息。
值反射(Value Reflection): 主要关注于程序运行时获取或设置变量的值。
类型反射
在反射中关于类型还划分为两种:类型(Type)和种类(Kind)。因为在Go语言中我们可以使用type关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)。 举个例子,我们定义了两个指针类型和两个结构体类型,通过反射查看它们的类型和种类。
Go语言的反射中像数组、切片、Map、指针
等类型的变量,它们的.Name()都是返回空。
https://pkg.go.dev/reflect#Kind
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
Pointer
Slice
String
Struct
UnsafePointer
)
package main
import (
"fmt"
"reflect"
)
type Human struct {
Name string `json:"name"`
Age int `json:"age"`
}
func inspector(data any) {
//看看Kind()的区别
t := reflect.TypeOf(data)
fmt.Println("name:", t.Name())
fmt.Println("kind:", t.Kind())
}
func main() {
fmt.Println("==============传入结构体========================")
var human Human = Human{
Name: "meow",
Age: 20,
}
inspector(human)
fmt.Println("===================================================")
fmt.Println()
fmt.Println("===============传入结构体指针=======================")
inspector(&human)
fmt.Println("===================================================")
fmt.Println()
fmt.Println("=================传入字符串=====================")
var a string = "hello world"
inspector(a)
fmt.Println("======================================")
fmt.Println()
fmt.Println("================传入字符串指针======================")
inspector(&a)
fmt.Println("======================================")
}
> ==============传入结构体========================
name: Human
kind: struct
===================================================
> ===============传入结构体指针=======================
name:
kind: ptr
===================================================
> =================传入字符串=====================
name: string
kind: string
======================================
> ================传入字符串指针======================
name:
kind: ptr
======================================
值反射
reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值的值信息。reflect.Value与原始值之间可以互相转换。
reflect.Value类型提供的获取原始值的方法如下:
package main
import (
"fmt"
"reflect"
)
type Human struct {
Name string `json:"name"`
Age int `json:"age"`
}
func inspector(data any) {
v := reflect.ValueOf(data)
if v.Kind() == reflect.Struct {
fmt.Println("是结构体")
} else if v.Kind() == reflect.Pointer {
ov := v.Elem()
if ov.Kind() == reflect.Struct {
fmt.Println("是结构体指针")
}
}
if v.Kind() == reflect.String {
fmt.Println("是字符串")
} else if v.Kind() == reflect.Pointer {
ov := v.Elem()
if ov.Kind() == reflect.String {
fmt.Println("是字符串指针")
}
}
}
func main() {
fmt.Println("==============传入结构体========================")
var human Human = Human{
Name: "meow",
Age: 20,
}
inspector(human)
fmt.Println("===================================================")
fmt.Println()
fmt.Println("===============传入结构体指针=======================")
inspector(&human)
fmt.Println("===================================================")
fmt.Println()
fmt.Println("=================传入字符串=====================")
var a string = "hello world"
inspector(a)
fmt.Println("======================================")
fmt.Println()
fmt.Println("================传入字符串指针======================")
inspector(&a)
fmt.Println("======================================")
}
==============传入结构体========================
是结构体
===================================================
===============传入结构体指针=======================
是结构体指针
===================================================
=================传入字符串=====================
是字符串
======================================
================传入字符串指针======================
是字符串指针
======================================
通过反射设置变量的值
想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。而反射中使用专有的Elem()方法来获取指针对应的值。
修改字符串:
package main
import (
"fmt"
"reflect"
)
type Human struct {
Name string `json:"name"`
Age int `json:"age"`
}
func inspector(data any) {
v := reflect.ValueOf(data)
if v.Elem().Kind() == reflect.String {
v.Elem().SetString("meowrain")
}
}
func main() {
str := "helloworld"
inspector(&str)
fmt.Println(str)
}
修改结构体
package main
import (
"fmt"
"reflect"
)
type Human struct {
Name string
}
func setStructVal(s any) {
v := reflect.ValueOf(s)
e := v.Elem()
t := reflect.TypeOf(s)
if t.Kind() != reflect.Ptr {
return
}
if t.Elem().Kind() != reflect.Struct {
return
}
for i := range e.NumField() {
e.Field(i).Set(reflect.ValueOf("sadfsd"))
}
}
func main() {
human := Human{
Name: "fsdafs",
}
setStructVal(&human)
fmt.Println(human)
}
反射的实际用途
透视数据组成
透视结构体组成,需要以下方法:
reflect.ValueOf():获取反射值对象;
reflect.Value.NumField():从结构体的反射值对象中获取它的字段个数;
reflect.Value.Field(i):从结构体的反射值对象中获取第i个字段的反射值对象;
reflect.Kind():从反射值对象中获取种类;
reflect.Int()/reflect.Uint()/reflect.String()/reflect.Bool():这些方法从反射值对象做取出具体类型。
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string
Age int
Married bool
}
func inspectStruct(u any) {
v := reflect.ValueOf(u)
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
switch field.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fmt.Printf("field: %d type:%s,value:%d\n", i, field.Type().Name(), field.Int())
case reflect.String:
fmt.Printf("field:%d type:%s,value:%q\n", i, field.Type().Name(), field.String())
case reflect.Bool:
fmt.Printf("field: %d,type:%s,value:%t", i, field.Type().Name(), field.Bool())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
fmt.Printf("field:%d type:%s value:%d\n", i, field.Type().Name(), field.Uint())
default:
fmt.Printf("field:%d unhandled kind:%s\n", i, field.Kind())
}
}
}
func main() {
u := User{
Name: "dj",
Age: 18,
Married: true,
}
inspectStruct(u)
}
结合使用reflect.Value的NumField()和Field()方法可以遍历结构体的每个字段。然后针对每个字段的Kind做相应的处理。
有些方法只有在原对象是某种特定类型时,才能调用。例如NumField()和Field()方法只有原对象是结构体时才能调用,否则会panic。
识别出具体类型后,可以调用反射值对象的对应类型方法获取具体类型的值,例如上面的field.Int()/field.Uint()/field.Bool()/field.String()。但是为了减轻处理的负担,Int()/Uint()方法对种类做了合并处理,它们只返回相应的最大范围的类型,Int()返回Int64类型,Uint()返回Uint64类型。而Int()/Uint()内部会对相应的有符号或无符号种类做处理,转为Int64/Uint64返回。
另外,我们也可以透视标准库中的结构体,并且可以透视其中的未导出字段。
package main
import (
"fmt"
"learn/other"
"reflect"
)
// 获取指定字段的值
func getField(ptr any, filedName string) reflect.Value {
v := reflect.ValueOf(ptr)
return v.Elem().FieldByName(filedName)
}
// 获取所有字段
func getAllFieldName(ptr any) {
t := reflect.TypeOf(ptr).Elem()
// 遍历结构体的字段
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("Field Name: %s, Field Type: %s, Is Exported: %v\n",
field.Name, // 字段名称
field.Type, // 字段类型
field.IsExported(), // 是否导出
)
}
}
func main() {
// 假设 other.Humans 是一个包含未导出字段的结构体
fmt.Println(other.Humans)
// 通过反射访问结构体字段
v := getField(&other.Humans, "name")
getAllFieldName(&other.Humans)
fmt.Println(v)
}
获取指定tag的结构体值
package main
import (
"fmt"
"reflect"
)
type Love struct {
Player1 string `json:"player1"` // 导出字段
Player2 string `json:"player2"` // 导出字段
}
func getFiledByTag(obj any, tagKey, tagValue string) (any, error) {
v := reflect.ValueOf(obj)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
t := v.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag.Get(tagKey)
if tag == tagValue {
return v.Field(i).Interface(), nil
}
}
return nil, fmt.Errorf("field with tag %s:%s not found", tagKey, tagValue)
}
func main() {
love := Love{
Player1: "fafas",
Player2: "fsadfas",
}
player1, err := getFiledByTag(&love, "json", "player1")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(player1.(string)) // 输出: fafas
}
}
私有变量不可导出