广告位联系
返回顶部
分享到

Go利用反射reflect实现获取接口变量信息

Golang 来源:互联网 作者:酷站 发布时间:2022-05-28 18:23:41 人浏览
摘要

反射是通过实体对象获取反射对象(Value、Type),然后可以操作相应的方法。在某些情况下,我们可能并不知道变量的具体类型,这时候就可以用反射来获取这个变量的类型或者方法。 一

反射是通过实体对象获取反射对象(Value、Type),然后可以操作相应的方法。在某些情况下,我们可能并不知道变量的具体类型,这时候就可以用反射来获取这个变量的类型或者方法。

一、反射的规则

其实反射的操作步骤非常的简单,就是通过实体对象获取反射对象(Value、Type),然后操作相应的方法即可。

下图描述了实例、Value、Type 三者之间的转换关系:

反射 API 的分类总结如下:

1、从实例到 Value

通过实例获取 Value 对象,直接使用 reflect.ValueOf() 函数。例如:

1

func ValueOf(i interface {}) Value

2、从实例到 Type

通过实例获取反射对象的 Type,直接使用 reflect.TypeOf() 函数。例如:

1

func TypeOf(i interface{}) Type

3、从 Type 到 Value

Type 里面只有类型信息,所以直接从一个 Type 接口变量里面是无法获得实例的 Value 的,但可以通过该 Type 构建一个新实例的 Value。reflect 包提供了两种方法,示例如下:

1

2

3

4

//New 返回的是一个 Value,该 Value 的 type 为 PtrTo(typ),即 Value 的 Type 是指定 typ 的指针类型

func New(typ Type) Value

//Zero 返回的是一个 typ 类型的零佳,注意返回的 Value 不能寻址,位不可改变

func Zero(typ Type) Value

如果知道一个类型值的底层存放地址,则还有一个函数是可以依据 type 和该地址值恢复出 Value 的。例如:

1

func NewAt(typ Type, p unsafe.Pointer) Value

4、从 Value 到 Type

从反射对象 Value 到 Type 可以直接调用 Value 的方法,因为 Value 内部存放着到 Type 类型的指针。例如:

1

func (v Value) Type() Type

5、从 Value 到实例

Value 本身就包含类型和值信息,reflect 提供了丰富的方法来实现从 Value 到实例的转换。例如:

1

2

3

4

5

6

7

8

9

//该方法最通用,用来将 Value 转换为空接口,该空接口内部存放具体类型实例

//可以使用接口类型查询去还原为具体的类型

func (v Value) Interface() (i interface{})

 

//Value 自身也提供丰富的方法,直接将 Value 转换为简单类型实例,如果类型不匹配,则直接引起 panic

func (v Value) Bool () bool

func (v Value) Float() float64

func (v Value) Int() int64

func (v Value) Uint() uint64

6、从 Value 的指针到值

从一个指针类型的 Value 获得值类型 Value 有两种方法,示例如下。

1

2

3

4

//如果 v 类型是接口,则 Elem() 返回接口绑定的实例的 Value,如采 v 类型是指针,则返回指针值的 Value,否则引起 panic

func (v Value) Elem() Value

//如果 v 是指针,则返回指针值的 Value,否则返回 v 自身,该函数不会引起 panic

func Indirect(v Value) Value

7、Type 指针和值的相互转换

指针类型 Type 到值类型 Type。例如:

1

2

3

//t 必须是 Array、Chan、Map、Ptr、Slice,否则会引起 panic

//Elem 返回的是其内部元素的 Type

t.Elem() Type

值类型 Type 到指针类型 Type。例如:

1

2

//PtrTo 返回的是指向 t 的指针型 Type

func PtrTo(t Type) Type

8、Value 值的可修改性

Value 值的修改涉及如下两个方法:

1

2

3

4

//通过 CanSet 判断是否能修改

func (v Value ) CanSet() bool

//通过 Set 进行修改

func (v Value ) Set(x Value)

Value 值在什么情况下可以修改?我们知道实例对象传递给接口的是一个完全的值拷贝,如果调用反射的方法 reflect.ValueOf() 传进去的是一个值类型变量, 则获得的 Value 实际上是原对象的一个副本,这个 Value 是无论如何也不能被修改的。

9、根据 Go 官方关于反射的文档,反射有三大定律:9

  • Reflection goes from interface value to reflection object.
  • Reflection goes from reflection object to interface value.
  • To modify a reflection object, the value must be settable.

第一条是最基本的:反射可以从接口值得到反射对象。

反射是一种检测存储在 interface中的类型和值机制。这可以通过 TypeOf函数和 ValueOf函数得到。

第二条实际上和第一条是相反的机制,反射可以从反射对象获得接口值。

它将 ValueOf的返回值通过 Interface()函数反向转变成 interface变量。

前两条就是说 接口型变量和 反射类型对象可以相互转化,反射类型对象实际上就是指的前面说的 reflect.Type和 reflect.Value。

第三条不太好懂:如果需要操作一个反射变量,则其值必须可以修改。

反射变量可设置的本质是它存储了原变量本身,这样对反射变量的操作,就会反映到原变量本身;反之,如果反射变量不能代表原变量,那么操作了反射变量,不会对原变量产生任何影响,这会给使用者带来疑惑。所以第二种情况在语言层面是不被允许的。

二、反射的使用

从relfect.Value中获取接口interface的信息

当执行reflect.ValueOf(interface)之后,就得到了一个类型为”relfect.Value”变量,可以通过它本身的Interface()方法获得接口变量的真实内容,然后可以通过类型判断进行转换,转换为原有真实类型。不过,我们可能是已知原有类型,也有可能是未知原有类型,因此,下面分两种情况进行说明。

1、已知原有类型

已知类型后转换为其对应的类型的做法如下,直接通过Interface方法然后强制转换,如下:

1

realValue := value.Interface().(已知的类型)

示例代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

package main

 

import (

    "fmt"

    "reflect"

)

 

func main() {

    var num float64 = 3.1415926

 

    pointer := reflect.ValueOf(&num)

    value := reflect.ValueOf(num)

 

    // 可以理解为“强制转换”,但是需要注意的时候,转换的时候,如果转换的类型不完全符合,则直接panic

    // Golang 对类型要求非常严格,类型一定要完全符合

    // 如下两个,一个是*float64,一个是float64,如果弄混,则会panic

    convertPointer := pointer.Interface().(*float64)

    convertValue := value.Interface().(float64)

 

    fmt.Println(convertPointer)

    fmt.Println(convertValue)

}

运行结果:

0xc000018080
3.1415926

说明

  • 转换的时候,如果转换的类型不完全符合,则直接panic,类型要求非常严格!
  • 转换的时候,要区分是指针还是指
  • 也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量”

2、未知原有类型

很多情况下,我们可能并不知道其具体类型,那么这个时候,该如何做呢?需要我们进行遍历探测其Filed来得知,示例如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

package main

 

import (

    "fmt"

    "reflect"

)

 

type Person struct {

    Name string

    Age int

    Sex string

}

 

func (p Person)Say(msg string)  {

    fmt.Println("hello,",msg)

}

func (p Person)PrintInfo()  {

    fmt.Printf("姓名:%s,年龄:%d,性别:%s\n",p.Name,p.Age,p.Sex)

}

 

func main() {

    p1 := Person{"王富贵",20,"男"}

 

    DoFiledAndMethod(p1)

 

}

 

// 通过接口来获取任意参数

func DoFiledAndMethod(input interface{}) {

 

    getType := reflect.TypeOf(input) //先获取input的类型

    fmt.Println("get Type is :", getType.Name()) // Person

    fmt.Println("get Kind is : ", getType.Kind()) // struct

 

    getValue := reflect.ValueOf(input)

    fmt.Println("get all Fields is:", getValue) //{王富贵 20 男}

 

    // 获取方法字段

    // 1. 先获取interface的reflect.Type,然后通过NumField进行遍历

    // 2. 再通过reflect.Type的Field获取其Field

    // 3. 最后通过Field的Interface()得到对应的value

    for i := 0; i < getType.NumField(); i++ {

        field := getType.Field(i)

        value := getValue.Field(i).Interface() //获取第i个值

        fmt.Printf("字段名称:%s, 字段类型:%s, 字段数值:%v \n", field.Name, field.Type, value)

    }

 

    // 通过反射,操作方法

    // 1. 先获取interface的reflect.Type,然后通过.NumMethod进行遍历

    // 2. 再公国reflect.Type的Method获取其Method

    for i := 0; i < getType.NumMethod(); i++ {

        method := getType.Method(i)

        fmt.Printf("方法名称:%s, 方法类型:%v \n", method.Name, method.Type)

    }

}

运行结果:

get Type is : Person
get Kind is :  struct
get all Fields is: {王富贵 20 男}
字段名称:Name, 字段类型:string, 字段数值:王富贵 
字段名称:Age, 字段类型:int, 字段数值:20 
字段名称:Sex, 字段类型:string, 字段数值:男 
方法名称:PrintInfo, 方法类型:func(main.Person) 
方法名称:Say, 方法类型:func(main.Person, string) 

总结

获取未知类型的interface的具体变量及其类型的步骤为:

  • 先获取interface的reflect.Type,然后通过NumField进行遍历
  • 再通过reflect.Type的Field获取其Field
  • 最后通过Field的Interface()得到对应的value

获取未知类型的interface的所属方法(函数)的步骤为:

  • 先获取interface的reflect.Type,然后通过NumMethod进行遍历
  • 再分别通过reflect.Type的Method获取对应的真实的方法(函数)
  • 最后对结果取其Name和Type得知具体的方法名
  • 也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量”
  • struct 或者 struct 的嵌套都是一样的判断处理方式

版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://juejin.cn/post/7102669653704441869
相关文章
  • 基于GORM实现CreateOrUpdate的方法
    CreateOrUpdate 是业务开发中很常见的场景,我们支持用户对某个业务实体进行创建/配置。希望实现的 repository 接口要达到以下两个要求: 如果
  • Golang中的内存逃逸的介绍
    什么是内存逃逸分析 内存逃逸分析是go的编译器在编译期间,根据变量的类型和作用域,确定变量是堆上还是栈上 简单说就是编译器在编译
  • Golang自旋锁的介绍
    自旋锁 获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting。 它是为实现保护共享资源而提出的
  • Go语言读写锁RWMutex的源码

    Go语言读写锁RWMutex的源码
    在前面两篇文章中初见 Go Mutex、Go Mutex 源码详解,我们学习了Go语言中的Mutex,它是一把互斥锁,每次只允许一个goroutine进入临界区,可以保
  • Go项目实现优雅关机与平滑重启功能
    什么是优雅关机? 优雅关机就是服务端关机命令发出后不是立即关机,而是等待当前还在处理的请求全部处理完毕后再退出程序,是一种对
  • Go语言操作Excel利器之excelize类库的介绍
    在开发中一些需求需要通过程序操作excel文档,例如导出excel、导入excel、向excel文档中插入图片、表格和图表等信息,使用Excelize就可以方便
  • 利用Go语言快速实现一个极简任务调度系统

    利用Go语言快速实现一个极简任务调度系统
    任务调度(Task Scheduling)是很多软件系统中的重要组成部分,字面上的意思是按照一定要求分配运行一些通常时间较长的脚本或程序。在爬
  • GoLang中的iface 和 eface 的区别介绍

    GoLang中的iface 和 eface 的区别介绍
    GoLang之iface 和 eface 的区别是什么? iface和eface都是 Go 中描述接口的底层结构体,区别在于iface描述的接口包含方法,而eface则是不包含任何方
  • Golang接口使用的教程
    go语言并没有面向对象的相关概念,go语言提到的接口和java、c++等语言提到的接口不同,它不会显示的说明实现了接口,没有继承、子类、
  • go colly 爬虫实现示例介绍
    贡献某CC,go源码爬虫一个,基于colly,效果是根据输入的浏览器cookie及excel必要行列号,从excel中读取公司名称,查询公司法人及电话号码。
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计