当前位置:   article > 正文

深入理解go语言反射机制_go反射创建对象

go反射创建对象

1、前言

       每当我们学习一个新的知识点时,一般来说,最关心两件事,一是该知识点的用法,另外就是使用场景。go反射机制作为go语言特性中一个比较高级的功能,我们也需要从上面两个方面去进行学习,前者告诉我们如何去使用,而后者告诉我们为什么以及什么时候使用。

2、反射机制

2.1 反射机制的基本概念

        在 Go 语言中,反射机制允许程序在运行时获取对象的类型信息、访问对象的字段和方法动态调用方法,甚至修改变量的值。反射的核心是 reflect 包,它提供了一组函数和类型来操作任意类型的值。

反射的基本概念

  1. 类型和值:反射通过 reflect.Typereflect.Value 来表示变量的类型和值。
  2. 类型检查:可以使用反射来检查变量的类型,包括基础类型和复杂类型。
  3. 值操作:可以通过反射读取和修改变量的值,但需要注意可设置性(settable)。

2.2 反射的基本用法

2.2.1 获取变量的类型和值

        可以通过reflect.TypeOf() 以及reflect.ValueOf()两个方法动态获取变量的类型和值。

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. func main() {
  7. var x float64 = 3.4
  8. // 通过reflect.TypeOf方法获取变量的类型
  9. // 返回类型为reflect.Type
  10. t := reflect.TypeOf(x)
  11. // 通过reflect.ValueOf方法获取变量的值
  12. // 返回类型为reflect.Value
  13. v := reflect.ValueOf(x)
  14. fmt.Println("Type:", t)
  15. fmt.Println("Value:", v)
  16. }

 程序打印如下:

Type: float64
Value: 3.4

2.2.2 修改反射对象的值

        通过 reflect.Value.CanSet 来判断一个反射对象是否是可设置的。如果是可设置的,我们就可以通过 reflect.Value.Set 来修改反射对象的值。

       那么什么是可设置的值呢?可设置性(settable)——反射中某些值是可修改的,而某些值是不可修改的。只有通过指针传递的值才能被修改。这是因为 Go 语言中的值传递特性——非指针变量的值是不可修改的。

        Go 语言中,变量是通过值传递的。对于普通变量(非指针变量),反射只能读取它们的值,不能修改它们。要使一个变量的值可通过反射修改,必须传递其地址(即指针),因为指针允许间接修改变量的值。

        一句话概括就是,值是不可更改的变量的引用(指针解引用),就可以修改

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. func main() {
  7. var x float64 = 3.4
  8. // 获取的是变量x的值
  9. v := reflect.ValueOf(x)
  10. // 获取的是变量x地址的值
  11. pv := reflect.ValueOf(&x)
  12. // 获取的是变量x的地址的解引用,就是变量x的引用
  13. refv := reflect.ValueOf(&x).Elem()
  14. fmt.Println("can set:", v.CanSet())
  15. fmt.Println("can set:", pv.CanSet())
  16. fmt.Println("can set:", refv.CanSet())
  17. if refv.CanSet() {
  18. refv.SetFloat(2.7)
  19. fmt.Println("value after change:", x)
  20. }
  21. }

输出结果如下:

can set: false
can set: false
can set: true
value after change: 2.7

 2.2.3 动态获取和修改结构体中的字段和值

        在2.2.1中提到可以通过reflect.TypeOf()以及reflect.ValueOf()两个方法来获取一个变量的类型和值,这两个方法的返回类型分别为reflect.Typereflect.Value。这两个类型还提供了一些非常有用的方法来处理结构体变量。

对于reflect.Type, 下面列出部方法:

  1. type Type interface {
  2. // 返回此类型的特定种类
  3. Kind() Kind
  4. // 返回结构类型的字段数量
  5. NumField() int
  6. // 返回结构体名称
  7. Name() string
  8. // 返回结构体种的第 i 个字段
  9. Field(i int) StructField
  10. // 返回具有给定名称的结构字段,并返回一个布尔值,指示是否找到该字段。
  11. FieldByName(name string) (StructField, bool)
  12. // 返回结构体中的第 i 个方法
  13. Method(i int) Method
  14. // 返回结构体中指定的方法,并返回是否找到该方法的bool值
  15. MethodByName(string) (Method, bool)
  16. // 返回可访问的方法数量
  17. NumMethod() int
  18. }

Kind方法额外作一点说明 

Kind()方法返回的是变量的类型所属的种类(额……听起来有点绕),举个栗子就明白了:

 

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type Person struct {
  7. Name string
  8. Age int
  9. }
  10. func main() {
  11. i := 100
  12. s := "hello"
  13. p := Person{"Alice", 30}
  14. fmt.Printf("type:%s, kind:%s\n", reflect.TypeOf(i), reflect.TypeOf(i).Kind())
  15. fmt.Printf("type:%s, kind:%s\n", reflect.TypeOf(s), reflect.TypeOf(s).Kind())
  16. fmt.Printf("type:%s, kind:%s\n", reflect.TypeOf(p), reflect.TypeOf(p).Kind())
  17. }

打印结果如下:

type:int, kind:int
type:string, kind:string
type:main.Person, kind:struct 

 简单总结就是reflect.TypeOf()方法得到的是变量的类型(int, *int, string, Person等),reflect.TypeOf().Kind()方法得到是变量类型所属的类别(int,ptr,string,struct)。

 reflect.Value 方法如下:

  • 获取信息类方法

Type():获取值的类型(返回 reflect.Type

Kind():获取值的种类(返回 reflect.Kind,如 reflect.Intreflect.Struct 等)。

Interface():将 reflect.Value 转换为 interface{},可以恢复为原始类型。

IsValid():检查值是否有效。

CanSet():检查值是否可设置。

  1. // Type方法获取值类型
  2. v := reflect.ValueOf(42)
  3. fmt.Println(v.Type()) // 输出: int
  4. v := reflect.ValueOf(42)
  5. // Kind方法获取类型的所属的类
  6. fmt.Println(v.Kind()) // 输出: int
  7. v := reflect.ValueOf(42)
  8. // Interface方法转换为接口
  9. i := v.Interface().(int)
  10. fmt.Println(i) // 输出: 42
  11. // IsValid判断值是否有效
  12. var v reflect.Value
  13. fmt.Println(v.IsValid()) // 输出: false
  14. // CanSet判断值是否可以设置
  15. v := reflect.ValueOf(42)
  16. fmt.Println(v.CanSet()) // 输出: false
  17. vp := reflect.ValueOf(&v).Elem()
  18. fmt.Println(vp.CanSet()) // 输出: true

  • 获取/修改基础类型的值方法

无论值多么复杂的结构,最终的字段都可以拆解为基础类型。如果refect.Type为基础类型,那么就可以获取值。

Int():获取整数值(适用于 intint8int16int32int64)。SetInt():设置整数值。

Float():获取浮点数值(适用于 float32float64)。SetFloat():设置浮点数值。

String():获取字符串值。SetString():设置字符串值。

Bool():获取布尔值。SetBool():设置布尔值。

  1. // 获取基础类型的值
  2. v := reflect.ValueOf(42)
  3. fmt.Println(v.Int()) // 输出: 42
  4. // 设置基础类型的值
  5. var x int64 = 42
  6. v := reflect.ValueOf(&x).Elem()
  7. v.SetInt(43)
  8. fmt.Println(x) // 输出: 43
  • 处理复合结构体

FieldByName():获取结构体字段的值。

Field(i):获取结构体第i个字段的值。

Elem():获取指针指向的(解引用)值。

  1. type Person struct {
  2. Name string
  3. Age int
  4. }
  5. p := Person{"Alice", 30}
  6. v := reflect.ValueOf(p)
  7. // 根据字段名获取字段值
  8. nameField := v.FieldByName("Name")
  9. fmt.Println(nameField.String()) // 输出: Alice
  10. // 获取第i个字段的值
  11. nameFieldI := v.Field(0)
  12. fmt.Println(nameField.String()) // 输出: Alice
  13. // 获取指针变量解引用的值
  14. x := 42
  15. v := reflect.ValueOf(&x)
  16. e := v.Elem()
  17. fmt.Println(e.Int()) // 输出: 42

2.2.4 获取变量的类型和值的用法总结 

  • 可以通过reflect.TypeOf()和reflect.ValueOf()分别获取变量的类型(reflect.Type)和值(reflect.Value)
  • 通过Kind方法可以获取一个值的类型所属的类别(int等基础类型、struct、ptr等)
  • 如果Kind方法返回的是基础类型,那么可以直接通过Int(),String()等方法获取变量的值
  • 如果Kind方法返回的是ptr类型,那么可以通过Elem()对指针解引用得到指针变量的值
  • 如果Kind方法返回的是struct类型,那么需要通过Field(i)或FieldByName()逐个获取每个字段的值,然后再根据每个字段的值的类型做进一步操作。

3、反射机制的使用场景

3.1 反射机制有什么用

        存在即是需要,反射机制这种语言特被开发出来,肯定是编程需要。

Go语言的反射(reflection)是指在程序运行时检查类型信息和变量值的能力。通过反射,我们可以在运行时动态地获取和修改对象的属性、方法和类型信息。

反射的作用主要有以下几个方面:

  1. 动态类型识别:反射可以在运行时动态地识别一个接口变量所存储的具体类型,包括基本类型、结构体类型、函数类型等。这样就可以根据具体类型来执行不同的操作。

  2. 动态创建对象:反射可以动态地创建一个对象的实例,包括结构体、数组、切片、Map等。这在编写通用代码时非常有用,可以根据输入参数的类型动态创建相应类型的对象。

  3. 动态调用方法和函数:反射可以在运行时动态地调用一个对象的方法或函数,包括公开的和私有的方法。这样就可以在不知道具体类型的情况下调用相应的方法或函数。

  4. 动态修改对象的属性:反射可以在运行时动态地修改对象的属性值,包括公开的和私有的属性。这在需要动态修改对象状态的情况下非常有用。

  5. 对结构体的字段进行遍历和操作:反射可以遍历一个结构体的所有字段,并对字段进行读取、修改等操作。这在需要根据结构体字段进行一些通用操作的场景下非常有用。

3.2 反射机制使用场景

Go 语言的反射机制允许在运行时检查和操作类型和值。反射通常用于以下几种场景:

  1. 通用库和框架开发:例如,ORM(对象关系映射)框架需要根据结构体定义来生成数据库查询。
  2. 序列化和反序列化:如 JSON 或 XML 的编解码,处理结构体字段与数据格式之间的转换。
  3. 单元测试:在测试中检查结构体或接口的类型和值。
  4. 动态调用方法和访问字段:在不知道具体类型的情况下,通过接口访问和修改对象。

以场景1为例:  

下面这个例子借用了用手写一个工具的过程讲清楚Go反射的使用方法和应用场景 文章中代码。

假如要根据一个结构体来生成查询数据库的SQL语句,该怎么做呢?

如果结构体是一个已知的类型,那么十分简单,关键代码只需要一行就能搞定:

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type order struct {
  6. ordId int
  7. customerId int
  8. }
  9. func createQuery(o order) string {
  10. i := fmt.Sprintf("INSERT INTO order VALUES(%d, %d)", o.ordId, o.customerId)
  11. return i
  12. }
  13. func main() {
  14. o := order{
  15. ordId: 1234,
  16. customerId: 567,
  17. }
  18. fmt.Println(createQuery(o))
  19. }

打印如下:

INSERT INTO order VALUES(1234, 567)

 分析下面这行关键代码:

fmt.Sprintf("INSERT INTO order VALUES(%d, %d)", o.ordId, o.customerId)

在生成Sql语句时,%d来格式化,说明我们知道插入的值的类型为int,另外,我们能够使用o.ordId以及o.customerId,说明我们知道结构中的字段是ordId以及customerId。

如果要写一个通用的方法,这样显然是不行的,因为不知道结构体中有哪些字段,更不知道这些字段的值的类型,这些都是需要动态获取的。

废话不多说,直接上代码:

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type order struct {
  7. ordId int
  8. customerId int
  9. }
  10. type employee struct {
  11. name string
  12. id int
  13. address string
  14. salary int
  15. country string
  16. }
  17. func createQuery(q interface{}) string {
  18. t := reflect.TypeOf(q)
  19. v := reflect.ValueOf(q)
  20. if v.Kind() != reflect.Struct {
  21. panic("unsupported argument type!")
  22. }
  23. tableName := t.Name() // 通过结构体类型提取出SQL的表名
  24. sql := fmt.Sprintf("INSERT INTO %s ", tableName)
  25. columns := "("
  26. values := "VALUES ("
  27. for i := 0; i < v.NumField(); i++ {
  28. // 注意reflect.Value 也实现了NumField,Kind这些方法
  29. // 这里的v.Field(i).Kind()等价于t.Field(i).Type.Kind()
  30. switch v.Field(i).Kind() {
  31. case reflect.Int:
  32. if i == 0 {
  33. columns += fmt.Sprintf("%s", t.Field(i).Name)
  34. values += fmt.Sprintf("%d", v.Field(i).Int())
  35. } else {
  36. columns += fmt.Sprintf(", %s", t.Field(i).Name)
  37. values += fmt.Sprintf(", %d", v.Field(i).Int())
  38. }
  39. case reflect.String:
  40. if i == 0 {
  41. columns += fmt.Sprintf("%s", t.Field(i).Name)
  42. values += fmt.Sprintf("'%s'", v.Field(i).String())
  43. } else {
  44. columns += fmt.Sprintf(", %s", t.Field(i).Name)
  45. values += fmt.Sprintf(", '%s'", v.Field(i).String())
  46. }
  47. }
  48. }
  49. columns += "); "
  50. values += "); "
  51. sql += columns + values
  52. fmt.Println(sql)
  53. return sql
  54. }
  55. func main() {
  56. o := order{
  57. ordId: 456,
  58. customerId: 56,
  59. }
  60. createQuery(o)
  61. e := employee{
  62. name: "Naveen",
  63. id: 565,
  64. address: "Coimbatore",
  65. salary: 90000,
  66. country: "India",
  67. }
  68. createQuery(e)
  69. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/767907
推荐阅读
相关标签
  

闽ICP备14008679号