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/json
marshal方法 (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 {}) Value
12.从实体到
Type
通过实例获取反射对象的
Type
,直接使用reflect.TypeOf()
函数.例如:func TypeOf(i interface{}) Type
13.从
Type
到Value
Type
里面只有类型信息,所以直接从一个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) Value
1
2
3
4
5如果知道一个类型值的底层存放地址,则还有一个函数是可以依据
type
和该地址值恢复出Value
的.例如:func NewAt(typ Type,p unsafe.Pointer) Value
14.从
Value
到Type
从反射对象
Value
到Type
可以直接调用Value
的方法,因为Value
内部存放这到Type
类型的指针.例如:func(v Value) Type() Type
15.从
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() uint64
1
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) Value
1
2
3
4
57.
Type
指针和值的相互转换指针类型
Type
到值类型Type
.例如://t 必须是 `Array、Chan、Map、Ptr、Slice`,否则会引起 panic //Elem 返回的是其内部元素的 Type t.Elem() Type
1
2
3值类型
Type
到指针类型Type
.例如://PtrTo 返回的是指向 t 的指针型 Type func PtrTo(t Type) Type
1
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 object
2.
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
- AWS NAT-NetWork-Firwalld配置(一)04-09
- 02
- AWS NAT-NetWork-Firwalld配置(二)04-09
- 03
- kubernetes部署minio对象存储01-18