对 Golang 中 reflect 反射包的示例

发布时间 2023-07-14 10:48:03作者: 2gbxzhdaz

引子


// 由于反射是基于类型系统 (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
}