Go语言反射(一)
# 反射reflect
- https://golang.google.cn/pkg/reflect/
# 一、引入reflect
官方Doc中Rob Pike给出的关于法神的定义
Reflection in computing is the ability of a program to examine its own structure,particularly through tyes;it's a from of metaprogramming. It's also a great source of confusion.(在计算机领域,反射一种让程序---主要是通过类型---理解其自身结构的一种能力.它是元编程的组成之一,同时它也是一大引入困惑的难题.)
# 维基百科
在计算机科学中,反射是指计算机程序运行时(
Run time)可以访问、检查和修改它本身状态或行为的一种能力.用比喻来说,反射就是程序字啊运行的时候能够"观察"并修改自身的行为.
不同语言的反射模型不尽相同,有些语言还不支持反射.<<Go 语言圣经>>中是这样定义反射的:
Go语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制
# 为什么要用反射
需要反射的2个场景:
- 1.有时需要编写一个函数,但是并不知道传递的参数类型是什么,可能是没有约定好;也可能是传入的类型很多,这些类型并不能统一表示.这是反射就会用的上了.
- 2.有时候需要根据某些条件决定调用哪个函数,比如根据用户自己的输入来决定.这时需要对函数和函数的参数进行反射,在运行期间动态地执行函数.
但是对于反射,还是有几点不太建议使用的理由:
- 1.与反射相关的代码,经常是难以阅读的.在软件工作中,代码可读性也是一个非常重要的指标.
- 2.
Go语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于发射代码是无能为力的.所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接panic,可能会造成严重的后果. - 3.反射性能影响还是比较大的,比正常代码运行速度慢一到两个数量级.所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性.
反射是计算机语言提供的一个关键特性,掌握它,对编写通用(不要写死)的代码有比较大的帮助,另外,一些库或者框架提供的关键特性也是通用反射来实现.
# 反射的概念
这里说的是反射是指反射编程, 根据维基百科的定义,反射编程是指在程序运行期间,可以访问,检测和修改它本身状态或者行为的一种能力.用比喻来说,反射就是程序运行的时候能够"观察"并且修改自己的行为的一种能力.
# 适用场景
- 不能预先知道函数参数类型,或者参数类型有很多种,无法用同一个类型来表示
- 函数需要根据入参来动态的执行不同的行为
# 反射的优缺点
# 反射的优点
- 可以在一定程序上避免硬编码,提供灵活性和通用性
- 可以作为一个第一个类对象发现并修改源代码的结构(如代码块,类,方法,协议等)
# 反射的缺点
- 由于将部分类型检查工作从编译期推迟到了运行时,使得一些隐藏的问题无法通过编译期发现,提高了代码出现
bug的几率,搞不好就会panic - 反射出变量的类型需要额外的开销,降低了代码的运行效率
- 反射的概念和语法比较抽象,过多的使用反射,使得代码难以被其他人读懂,不利于合作与交流
# Golang反射的基本原理
Golang是怎么实现在程序运行的时候能够"观察"并且修改自己的行为的能力的呢
Golang反射是通过接口来实现的,通过隐式转换,普通的类型被转换成interface类型,这个过程涉及到类型转换的过程,首先从Golang类型转为interface类型, 再从interface类型转换成反射类型, 再从反射类型得到想的类型和值的信息.
总的基本流程见下面的图.

在Golang obj转成interface这个过程中, 分2种类型
- 包含方法的
interface, 由runtime.iface实现 - 不包含方法的
interface, 由runtime.eface实现
这2个类型都是包含2个指针, 一个是类型指针, 一个是数据指针, 这2个指针是完成反射的基础.
实质上, 通过上述转换后得到的2种interface, 已经可以实现反射的能力了. 但作为语言本身, 标准库将这个工作封装好了, 就是 reflect.Type与reflect.Value , 方便使用反射.
reflect. TypeOf 和 reflect.ValueOf 是一个转换器, 完成反射的的最终转换, 得到 reflect.Type, reflect.Value 对象, 得到这2个对象后, 就可以完成反射的准备工作了, 通过 reflect.Type, reflect.Value 这对类型, 可以实现反射的能力.
上图中, 最后一根线说的是由reflect. Value变成普通inteface的过程, 然后通过具体的类型断言, 转成真正的类型. 可能有人会觉得奇怪, 为什么 reflect.Value 可以转成interface对象, reflect. Type 不行.
# Golang反射提供的能力
- 运行时获取对象的类型, 值
- 创建对象, 执行方法
- 反射对象转换成
Go语言对象 - 动态修改对象的值
# Go反射三定律
如同物理反射定律一样, 反射编程中也有反射定律. 这个反射定律是在go语言官方博客中, The Laws of Reflection (opens new window) .简单概括一下就是下面三点:
Golang对象可以转换成反射对象- 反射对象可以转换成
Golang对象 - 可寻址的
reflect对象可以更新值
# Golang中反射的实际运用
反射在标准库中和第三方库中有着大量的运用,如下简单示例:
encoding/jsonmarshal方法 (opens new window)fmt. Printf- 各种
orm工具
json序列化的例子
func (e *encodeState) marshal(v interface{}, opts encOpts) (err error) {
defer func() {
if r := recover(); r != nil {
if je, ok := r.(jsonError); ok {
err = je.error
} else {
panic(r)
}
}
}()
e.reflectValue(reflect.ValueOf(v), opts)
return nil
}
2
3
4
5
6
7
8
9
10
11
12
13
这里将interface v通过 reflect.ValueOf 转换成 reflect.Value 对象进一步做下处理.
func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) {
valueEncoder(v)(e, v, opts)
}
func valueEncoder(v reflect.Value) encoderFunc {
if !v.IsValid() {
return invalidValueEncoder
}
return typeEncoder(v.Type())
}
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
// If we have a non-pointer value whose type implements
// Marshaler with a value receiver, then we're better off taking
// the address of the value - otherwise we end up with an
// allocation as we cast the value to an interface.
if t.Kind() != reflect.Ptr && allowAddr && reflect.PtrTo(t).Implements(marshalerType) {
return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false))
}
if t.Implements(marshalerType) {
return marshalerEncoder
}
if t.Kind() != reflect.Ptr && allowAddr && reflect.PtrTo(t).Implements(textMarshalerType) {
return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false))
}
if t.Implements(textMarshalerType) {
return textMarshalerEncoder
}
switch t.Kind() {
case reflect.Bool:
return boolEncoder
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return intEncoder
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return uintEncoder
case reflect.Float32:
return float32Encoder
case reflect.Float64:
return float64Encoder
case reflect.String:
return stringEncoder
case reflect.Interface:
return interfaceEncoder
case reflect.Struct:
return newStructEncoder(t)
case reflect.Map:
return newMapEncoder(t)
case reflect.Slice:
return newSliceEncoder(t)
case reflect.Array:
return newArrayEncoder(t)
case reflect.Ptr:
return newPtrEncoder(t)
default:
return unsupportedTypeEncoder
}
}
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
56
57
58
这里通过 reflect.Type 类型, 来初始化不同的encoder, 大量运用了反射, 实现了序列化, 在不支持反射的语言如c++, 实现对象json序列化, 就比较麻烦.
# 总结
Golang反射严重依赖于 interface{} 这个万能的 容器 类型, 这个 interface{} 类型相当于java中的class类型, 是实现反射的桥梁. 我们在谈Golang反射时, 主要还是围绕 interface{} 展开来说的.
反射是现代静态语言的通用底层技术, 能在一定程序上提升静态类型的灵活性.
# 二、相关基础
反射是和实现的?Go中的interface,它是Go语言实现抽象的一个非常强大的工具.当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息,反射就是通过接口的类型信息实现的,反射建立在类型的基础上.
Go语言在reflect包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值.在就进行更加详细的了解之前,需要重新回顾Go语言相关的一些特性,从这些特性中了解其反射机制是如何使用的.
| 特点 | 说明 |
|---|---|
go语言是静态类型语言. | 编译时类型已经确定,比如对已基本数据类型的再定义后的类型,反射时候需要确认返回的是何种类型. |
空接口interface{} | go的反射机制是要通过接口来进行的,而类似于Java的Object的空接口可以和任何类型进行交互,因此对基本数据类型等的反射也直接利用了这一特点. |
Go语言的类型:
变量包括
(type,value)量部分,理解这一点就知道为什么nil != nil了type包括static type和concrete type.简单来说static type是在编码看见的类型(如int 、string),concrete type是runtime系统看见的类型类型断言能否成功,取决于变量的
concrete type,而不是static type.因此,一个reader变量如果它的concrete type也实现了write方法的话,它也可以被类型断言为writer.
Go是静态类型语言,每个变量都拥有一个静态类型,这意味着每个变量类型在编译时都是确定的: int,float32,*AutoType,[]byte,chan []int诸如此类.
在反射的概念中,编译时就知道变量类型的是静态类型;运行时才知道一个变量类型的叫做动态类型.
静态类型
静态类型就是变量声明时的赋予的类型.比如:
type MyInt int //就是静态类型
type A struct {
Name string //string就是静态
}
var i *int // *int就是静态类型
2
3
4
5
6
动态类型
动态类型: 运行时给这个变量赋值时,这个值的类型(如果值为
nil的时候没有动态类型).一个变量的动态类型在运行时可能改变,这主要依赖于它的赋值(前提是这个变量是接口类型).var A interface{} //静态类型interface{} A = 10 //静态类型为interface{} 动态为int A = "String" //静态类型为interface{} 动态为string var M *int A = M // A的值可以改变1
2
3
4
5
Go语言的反射就是建立在类型之上的,Golang的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是static type),在创建变量的时候就已经确定,反射主要与Golang的interface类型相关(它的type是concrete type),只有interface类型才有发射一说.
在Golang的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:
(value,type)
value是实际变量值,type是实际变量的类型,一个interface{}类型的变量包含了2个指针,一个指针指向值的类型[对应concrete type],另一个指针指向实际的值[对应value].
例如,创建类型为*os.File的变量,然后将其赋给一个接口变量r:
tty, err := os.OpenFile("/dev/tty",os.O_RDWR,0)
var r io.Reader
r = tty
2
3
4
接口变量r的pair中将记录如下信息: (tty, *os.File),这个pair在接口变量的连接赋值过程中是不变的,将接口变量r赋给另一个接口变量w:
var w io.Writer
w = r.(io.Writer)
2
接口变量w的pair与r的pair相同,都是: (tty, *os.File),即使w是空接口类型,pair也不是不变的.
interface及其pair的存在,是Golang中实现反射的前提,理解了pair,就更容易理解反射.反射就是用来检测存储在接口变量内部(值vlue;类型concrete type)pair对的一种机制.
所以要理解两个基本概念Type和Value,它们也是Go语言中reflect空间里最重要的两个类型.
# 三、反射的规则
其实反射的操作步骤非常简单,就是通过实体对象获取反射对象(Value、Type),然后操作相应的方法即可.
下面描述了实例、Value、Type三者之间的转换关系:

# 反射API的分类:
1.从实体到
Value通过实例获取
Value对象,直接使用reflect.ValueOf()函数.例如:func ValueOf(i interface {}) Value12.从实体到
Type通过实例获取反射对象的
Type,直接使用reflect.TypeOf()函数.例如:func TypeOf(i interface{}) Type13.从
Type到ValueType里面只有类型信息,所以直接从一个Type接口变量里面是无法获得实例的Value的,但可以通过该Type构建一个新实例的Value.reflect包提供了两种方法,示例如下:// New 返回的是一个`Value`,该`Value`的`type`为`PtrTo(typ)`,即`Value`是指定`typ`的指针类型 func New(typ Type) Value //Zero 返回的是一个 `typ`类型的零值,注意返回的`Value`不能寻址,位不可改变 func Zero(typ Type) Value1
2
3
4
5如果知道一个类型值的底层存放地址,则还有一个函数是可以依据
type和该地址值恢复出Value的.例如:func NewAt(typ Type,p unsafe.Pointer) Value14.从
Value到Type从反射对象
Value到Type可以直接调用Value的方法,因为Value内部存放这到Type类型的指针.例如:func(v Value) Type() Type15.从
Value到实例Value本身就包含类型和值信息,reflect提供了丰富的方法来实现从Value到实例的转换.例如://该方法最通用,用来将`Value`转换为空接口,该空接口内部存放具体类型实例 //可以使用接口类型查询去还原为具体的类型 //`Value`自身也提供丰富的方法,直接将`Value`转换为简单类型实例,如果类型不匹配,则直接引起`panic` func (v Value) Bool () bool func (v Value) Float() float64 func (v Value) Int() int64 func (v Value) Uint() uint641
2
3
4
5
6
7
86.从
Value的指针到值从一个指针类型的
Value获得值类型Value有两种方法,示例如下.//如果 v 类型是接口,则`Elcm()`返回接口绑定的实例的`Value`,如采 v 类型是指针,则返回指针值的`Value`,否则引起panic func (v Value) Elem() Value //如果 v 是指针,则返回指针值的`Value`,否则返回 v 自身,该函数不会引起 panic func Indirect(v Value) Value1
2
3
4
57.
Type指针和值的相互转换指针类型
Type到值类型Type.例如://t 必须是 `Array、Chan、Map、Ptr、Slice`,否则会引起 panic //Elem 返回的是其内部元素的 Type t.Elem() Type1
2
3值类型
Type到指针类型Type.例如://PtrTo 返回的是指向 t 的指针型 Type func PtrTo(t Type) Type1
28.
Value值的可修改性Value值的修改涉及如下两个方法://通过 CanSet 判断是否能修改 func (v value) CanSet() bool //通过 Set 进行修改 func (v Value) Set(x Value)1
2
3
4
5
Value值是在什么情况下可以修改? 在知道示例对象传递给接口的是一个完全的值考本,如果调用反射的方法reflect.ValueOf()传进去的是一个值类型变量,则获得的Value实际上是原对象的一个副本,这个Value是无论如何也不能被修改的.
根据Go官方关于反射的博客,反射有三大定律:
1.
Reflection goes from interface value to reflection object2.
Reflection goes from reflection object to interface value.3.
To modify a reflection object,the value must be settable.
第一条是最基本的: 反射可以从接口值得发哦反射对象
反射是一种检测存储在
interface中的类型和值机制.这可以通过TypeOf函数和ValueOf函数得到.第二条实际上和第一条是相反的机制,反射可以从反射对象获得接口值.
它将
ValueOf的返回值通过interface{}函数反向转变成interface变量.
前两条就是说接口型变量和反射类型对象可以相互转化,反射类型对象实际上就是指的前面说的reflect.Type和reflect.Value
第三条不太好懂: 如果需要操作一个反射变量,则其值必须可以修改.
反射变量可设置的本质是它存储了原变量本身,这样反射变量的操作,就会反映到原变量本身;反之,如果反射变量不能代表原变量,那么操作了反射变量,不会对原变量产生任何影响,这会给使用者带来疑惑.所以第二种情况在语言层面是不被允许的.
# 四、反射的使用
- 一般用到的包是
reflect
# 3.1.reflect的基本功能TypeOf和ValueOf
既然反射就是用来检测存储在接口变量内部(值value;类型concrete type)pair的对的一种机制.那么在Golang的reflect反射包中有什么样的方式可以让直接获取到变量内部的信息呢?它提供了两种类型(或者说两个方法)可以很容易的访问接口变量内容,分别是reflect.ValueOf()和reflect.TypeOf(),官方的解释.
//ValueOf returns a new Value initialized to the concrete value
//stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {...}
翻译: `ValueOf`用来获取输入参数接口中的数据的值,如果接口为空则返回`0`
//TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {...}
翻译: `typeOf`用来动态获取输入参数接口中的值的类型,如果接口为空则返回`nil`
2
3
4
5
6
7
8
9
10
11
reflect.TypeOf()是获取pair中的type,reflect.ValueOf()获取pair中的value.
使用reflect一般分成三步:
首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)
t := reflect.TypeOf(i) //得到类型的元数据,通过`t`能获取类型定义里面的所有元素
v := reflect.ValueOf(i) //得到实际的值,通过`v`获得存储在里面的值,还可以去改变值
2
示例代码
package main
import (
"fmt"
"reflect"
)
func main() {
/*
反射操作: 通过反射,可以获取一个接口类型变量的 类型和数值
*/
var x float64 = 3.4
fmt.Println("type: ", reflect.TypeOf(x))
fmt.Println("value: ", reflect.ValueOf(x))
fmt.Println("-----------------------------")
//根据反射的值,来获取对应的类型和数值
v := reflect.ValueOf(x)
fmt.Println("kind is float64: ", v.Kind() == reflect.Float64)
fmt.Println("type: ", v.Type())
fmt.Println("value: ", v.Float())
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
执行结果

# 3.2.从reflect.Value中获取接口interface的信息
当执行reflect.ValueOf(interface)之后,就得到了一个类型为reflect.Value变量.可以通过本身的Iterface()方法获得接口变量的真是内容,然后可以通过类型判断进行转换,转换为原有真是类型.不过,可能是已知原有类型,也有可能是未知原有类型,因此,下面分两种情况进行说明:
# 已知原有类型
已知类型后转换为其对应的类型的做法如下,直接通过Iterface方法然后强制转换,如下:
realValue := value.Interface().(已知的类型)
示例代码
package main
import (
"fmt"
"reflect"
)
func main() {
var num float64 = 1.23
//"接口类型变量" ---> "反射类型对象"
value := reflect.ValueOf(num)
//"反射类型对象" ---> "接口类型变量"
convertValue := value.Interface().(float64)
fmt.Println(convertValue)
/*
反射类型对象 ---> 接口类型变量,理解为"强制转换"
Golang对类型要求非常严格,类型一定要完全符合
一个是*float64,一个float64,如果弄混,直接panic
*/
pointer := reflect.ValueOf(&num)
convertPointer := pointer.Interface().(*float64)
fmt.Println(convertPointer)
}
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
运行结果
1.23
0xc0000ba058
2
说明:
- 1.转换的时候,如果转换的类型不完全符合,则直接
panic,类型要求非常严格! - 2.转换的时候,要区分是指针还是值
- 3.也就是说反射可以将"反射类型对象"再重新转换为"接口类型变量"
# 未知原有类型
很多情况下,可能并不知道其具体类型,那么这个时候,该如何做呢?需要进行遍历探测器Filed来得知,示例如下:
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{"王二狗",30,"男"}
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) //{王二狗 30 男}
// 获取方法字段
// 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)
}
}
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
56
57
58
59
60
执行结果


说明
通过运行结果可以得知获取未知类型的interface的具体变量及其类型的步骤为:
- 1.想获取
interface的reflect.Type,然后通过NumField进行遍历 - 2.在通过
reflect.Type的Field获取其Field - 3.最后通过
Field的Interface()得到对应的value
通过运行结果可以得知获取未知类型的interface的所属方法(函数)的步骤为:
- 1.先获取
interface的reflect.Type,然后通过NumMethod进行遍历 - 2.再分别通过
reflect.Type的Method获取对应的真是的方法(函数) - 最后对结果取其
Name和Type得知具体的方法名 - 也就是说反射可以将"反射类型对象"再重新转换为"接口类型变量"
struct或者struct的嵌套都是一样的判断处理方式.
kind有slice,map,pointer指针,struct,interface,string,Array,Function,int或其他基本类型组成.kind和Type之前要做好区分.如果定义一个type Person struct{},那么Kind就是struct,Type就是Person.
反射变量对应的Kind方法的返回值是基本类型,并不是静态类型.
type myint int
var x myint = 100
v := reflect.ValueOf(X)
2
3
变量
v的Kind依旧是reflect.int,而不是myint这个静态类型.Type可以表示静态类型,而Kind不可以.
# 3.3.通过reflect.Value设置实际变量的值
reflect.Value是通过reflect.ValueOf(X)获得的,只有当X是指针的时候,才可以通过reflect.Value修改实际变量X的值,即: 要修改反射类型的对象就一定要保证其值是addressable的.
也就是说: 要想修改一个变量的值,那么必须通过该变量的指针地址,取消指针的引用.通过refPtrVal:= reflect.ValueOf(&var)的方式获取指针类型,使用refPtrVal.elem().set (一个新的reflect.Value)来进行更改,传递给set()的值也必须是一个reflect.value.
这里需要一个方法:

解释起来: Elem返回接口v包含的值或指针v指向的值.如果v的类型不是interface或ptr,它会恐慌.如果v为零,则返回零值.
如果变量是一个指针、map、slice、channel、Array.那么可以使用reflect.TypeOf(v).Elem()来确定包含的类型.
示例代码
package main
import (
"fmt"
"reflect"
)
func main() {
var num float64 = 1.23
fmt.Println("num的数值是: ", num)
//需要操作指针
//通过reflect.ValueOf() 获取num的Value对象
pointer := reflect.ValueOf(&num)
newValue := pointer.Elem()
fmt.Println("类型: ", newValue.Type()) //float64
fmt.Println("是否修改数据: ", newValue.CanSet()) //true
//重新赋值
newValue.SetFloat(3.14)
fmt.Println(num)
//如果reflect.ValueOf地参数不是指针
value := reflect.ValueOf(num)
//value.SetFloat(6.28) // panic: reflect: reflect.Value.SetFloat using unaddressable value
fmt.Println(value.CanSet())
//value.Elem() //如果非指针,会直接panic: reflect: call of reflect.Value.Elem on float64 Value
}
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
执行结果
num的数值是: 1.23
类型: float64
是否修改数据: true
3.14
false
2
3
4
5
说明
1.需要传入的参数
*float64这个指针,然后可以通过pointer.Elem()去获取所指向的Value,注意一定要是指针.2.如果传入的参数不是指针,而是变量,那么
- 通过
Elem获取原始值对应的对象则直接panic - 通过
CanSet方法查询是否可以设置返回false
- 通过
3.
newValue.CantSet()表示是否可以重新设置其值,如果输出的是true则可以修改,否则不能修改,修改完之后再进行打印发现真的已经修改了.4.
reflect.Value.Elem()表示获取原始值对应的反射对象,只有原始对象才能修改,当前反射对象是不能修改的5.也就是说如果要修改反射类型对象,其值必须是
addressable[对应的要传入的是指针,同时要通过Elem方法获取原始值对应的反射对象]6.
struct或者struct的嵌套都是一样的判断处理方式.
尝试修改结构体中的字段数值
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
School string
}
func main() {
s1 := Student{"王二狗", 19, "清华大学"}
//通过反射,更改对象的数值: 前提也是数据可以被更改
fmt.Printf("%T\n", s1)
p1 := &s1
fmt.Printf("%T\n", p1)
fmt.Println(s1.Name)
fmt.Println((*p1).Name, p1.Name)
//修改数值
value := reflect.ValueOf(&s1)
if value.Kind() == reflect.Ptr {
newValue := value.Elem()
fmt.Println(newValue.CanSet()) //true
f1 := newValue.FieldByName("Name")
f1.SetString("布鲁斯")
f2 := newValue.FieldByName("School")
f2.SetString("北京大学")
fmt.Println(s1)
}
}
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
执行结果
main.Student
*main.Student
王二狗
王二狗 王二狗
true
{布鲁斯 19 北京大学}
2
3
4
5
6
练习
- 对
silce、map、channel中的数据进行修改
# 3.4.通过reflect.Value来进行方法的调用
这算是一个高级用法了,前面只是介绍了对类型、变量的几种反射的用法,包括如何获取其值、其类型、以及如何重新设置新值.但是在项目应用中,另外一个常用并且属于高级的用法,就是通过reflect来进行方法[函数]的调用.比如要做架构工程的时候,需要可以随意扩展方法,或者说用户可以自定义方法,那么通过什么手段来扩展让用户能够自定义呢?关键点在于用于的自定义方法是未可知的,因为可以通过reflect来搞定.
Call()方法:

通过反射,调用方法
先获取结构体对象,然后
示例代码
package main
import (
"fmt"
"reflect"
)
type Person1 struct {
Name string
Age int
Sex string
}
//有参数的方法
func (p Person1) Say1(msg string) {
fmt.Println("hello", msg)
}
//无参数的方法
func (p Person1) PrintInfo1() {
fmt.Printf("姓名: %s,年龄: %d,性别: %s\n", p.Name, p.Age, p.Sex)
}
func (p Person1) Test(i, j int, s string) {
fmt.Println(i, j, s)
}
func main() {
/*
通过反射来进行方法的调用
思路:
step1: 接口变零 ---> 对象反射对象: Value
step2: 获取对应的方法对象:
step3: 将方法对象进行调用: Call()
*/
p1 := Person1{"Bruce", 20, "男"}
value := reflect.ValueOf(p1)
fmt.Printf("kind: %s,type: %s\n", value.Kind(), value.Type()) //kind: func,type: func()
methodValue := value.MethodByName("PrintInfo1")
fmt.Printf("kind: %s,type: %s\n", methodValue.Kind(), methodValue.Type()) // kind: struct,type: main.Person1
//没有参数,进行调用
methodValue.Call(nil) //没有参数传nil
args1 := make([]reflect.Value, 0) //或者是创建一个空的切片也可以
methodValue.Call(args1)
methodValue1 := value.MethodByName("Say1")
fmt.Printf("kind: %s,type: %s\n", methodValue1.Kind(), methodValue1.Type()) // kind: func,type: func(string)
args2 := []reflect.Value{reflect.ValueOf("反射机制")}
methodValue1.Call(args2)
methodValue2 := value.MethodByName("Test")
fmt.Printf("kind: %s,type: %s\n", methodValue2.Kind(), methodValue2.Type())
args3 := []reflect.Value{reflect.ValueOf(100), reflect.ValueOf(200), reflect.ValueOf("hello world")}
methodValue2.Call(args3)
}
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
56
57
58
59
60
61
运行结果
kind: struct,type: main.Person1
kind: func,type: func()
姓名: Bruce,年龄: 20,性别: 男
姓名: Bruce,年龄: 20,性别: 男
kind: func,type: func(string)
hello 反射机制
kind: func,type: func(int, int, string)
100 200 hello world
2
3
4
5
6
7
8
# 通过反射,调用函数
首选要确认一点,函数像普通的变量一样,是可以把函数作为一种变量类型的,而且是引用类型.如果说Fun()是一个函数,那么f1 := Fun也是可以的,那么f1也是一个函数,如果直接调用f1(),那么运行的就是Fun()函数.
那么就先通过ValueOf()来获取函数的反射对象,可以判断它的Kind,是一个func,那么就可以执行Call()进行含糊的调用.
示例代码
package main
import (
"fmt"
"reflect"
"strconv"
)
func main() {
//函数的反射
/*
思路: 函数也是看做接口变量类型
step1: 函数 ---> 反射对象,Value
step2: kind ---> func
step3: call()
*/
f1 := fun1
value := reflect.ValueOf(f1)
fmt.Printf("kind: %s,type: %s\n", value.Kind(), value.Type())
value2 := reflect.ValueOf(fun2)
value3 := reflect.ValueOf(fun3)
fmt.Printf("kind: %s,type: %s\n", value2.Kind(), value2.Type())
fmt.Printf("kind: %s,type: %s\n", value3.Kind(), value3.Type())
//通过反射调用函数
value.Call(nil)
value2.Call([]reflect.Value{reflect.ValueOf(1000), reflect.ValueOf("bruce")})
resultValue := value3.Call([]reflect.Value{reflect.ValueOf(2000), reflect.ValueOf("班纳")})
fmt.Printf("%T\n", resultValue) //[]reflect.Value
fmt.Println(len(resultValue))
fmt.Printf("kind: %s,type: %s\n", resultValue[0].Kind(), resultValue[0].Type()) //kind: string,type: string
s := resultValue[0].Interface().(string)
fmt.Println(s)
fmt.Printf("%T\n", s)
}
func fun1() {
fmt.Println("我是函数fun1(),无参的...")
}
func fun2(i int, s string) {
fmt.Println("我是函数fun2(),有参的...", i, s)
}
func fun3(i int, s string) string {
fmt.Println("我是函数fun3(),有参的,也有返回值...", i, s)
return s + strconv.Itoa(i)
}
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
执行结果
kind: func,type: func(int, string)
kind: func,type: func(int, string) string
我是函数fun1(),无参的...
我是函数fun2(),有参的... 1000 bruce
我是函数fun3(),有参的,也有返回值... 2000 班纳
[]reflect.Value
1
kind: string,type: string
班纳2000
string
2
3
4
5
6
7
8
9
10
说明
- 1.要通过反射来调用起对应的方法,必须要先通过
reflect.ValueOf(interface)来湖区到reflect.Value,得到"反射类型对象"后才能做下一步处理 - 2.
reflect.Value.MethodByName这里MethodByName,需要指定准确真实的方法名字,如果错误将直接panic,MethodByName返回一个函数值对应的reflect.Value方法的名字. - 3.
[]reflect.Value,这个是最终需要调用的方法的参数,可以没有或者一个或者多个,根据实际参数来定. - 4.
reflect.Value的Call这个方法,这个方法最终调用真实的方法,参数务必保持一致,如果reflect.Value.Kind不是一个方法,那么将直接panic - 5.本来可以用对象访问方法直接调用的,但是如果要通过反射,那么首先要将方法注册,也及时
methodByName,然后通过反射调用methodValue.Call
- 01
- kubernetes部署minio对象存储01-18
- 02
- Jenkins-Argocd CICD-下01-17
- 03
- Jenkins-Argocd CICD Rollouts金丝雀发布优化01-17