引言
反射是通过实体对象获取反射对象(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 的嵌套都是一样的判断处理方式
以上就是Go利用反射reflect实现获取接口变量信息的详细内容,更多关于Go reflect获取接口信息的资料请关注服务器之家其它相关文章!
原文链接:https://juejin.cn/post/7102669653704441869