反射机制是一个很重要的内容,当咱们写框架的时候,要想要松耦合,高复用,那么就有不少地方都须要用到反射,可谓是中高级程序员必须掌握的知识点程序员
不少后台语言都有反射机制,但它们的使用原理大多都是同样的微信
各语言不一样的地方,大体就是代码实现方式不一致罢了框架
其根本,都是从变量获得反射对象,再由反射对象去操做原变量函数
好了,步入正题单元测试
我就用一句话来归纳吧学习
使用反射,可让咱们在程序运行时对任意类型的对象进行操做测试
注意操做这两个字,操做是指:能够获取对象的信息、改变对象的值、调用对象的方法、甚至是建立一个对象spa
说到这你可能有点困惑,咱们在编写代码的时候不就已经把该实例化的象进行了实例化,该调用的方法都调用了嘛?为何写程序的时候不调用方法,偏要在运行时去进行这些操做?指针
其实问题就在这里,若是咱们在写程序的时候,一切的对象与方法都可以肯定了,那还要反射作什么?code
正是由于咱们在写程序的时候,要想写一些“万能程序”,用于下降代码的耦合度,因此咱们才须要反射,用于处理一些未知的对象
想一想,当咱们写一个方法,无论别人往咱们这个方法内传入什么样的参数,最后咱们的函数都能给别人所须要的内容。是否是感受很牛逼?
我这里主要说使用反射的原理,并非刨析反射的底层原理,有兴趣想要探索原理的读者大人,能够去看看go的reflect包源码
先给大家上个图,看懂这个关系图,后面的文字基本也就能够不看了
没看懂不要紧,稍微解释就能明白~~
咱们定义的一个变量,不论是基本类型int
,仍是一个结构体Employee
,咱们均可以经过reflect.TypeOf()
获取他的反射类型Type
,也能够经过reflect.ValueOf()
去获取他的反射值Value
咱们学习反射,其实就是学习如何使用原变量,去取得reflect.Type
或者reflect.Value
这种反射对象;再使用这个反射对象Type
以及Value
,反过来对原变量进行操做
弄明白了这个道理,那一切都将变得简单
剩下的,咱们只是须要去学习reflect
包中提供的方法。当咱们须要要怎么操做变量,就使用其提供的对应方法便可
Type
与Kind
的区别是什么?Type
是类型,Kind
是类别,听起来有点绕,他们之间的关系为Type
是Kind
的子集
若是变量是基本类型,那么Type
与Kind
获得的结果是一致的,好比变量为int
类型,Type
与Kind
的值相等,都为int
但当变量为结构体时,Type
与Kind
的值就不同了
咱们来看个实际案例
func main() {
var emp Employee
emp = Employee{
Name: "naonao",
Age: 99,
}
rVal := reflect.ValueOf(emp)
log.Printf("Kind is %v ,Type is %v",
rVal.Kind(),
rVal.Type())
// Kind is struct ,Type is main.Employee
}
复制代码
能够看到,Kind
的值是struct
,而Type
的值是包名.Employee
reflect.Value
之间切换?变量能够转换成interface{}
以后,再转换成reflect.Value
类型,既然空接口能够转换成Value
类型,那么天然也能够反过来转换成变量
用个表达式来表示,就以下所示
变量<----->interface{}<----->reflect.Value
利用空接口来进行中转,这样变量
与Value
之间就能够实现互相转换了
下面咱们再说如何用代码实现转换
这里咱们要注意一下,reflect.ValueOf()
获得的值是reflect.Value
类型,并非变量自己的值
var num = 1
rVal := reflect.ValueOf(num)
log.Printf("num is %v", num + rVal)
复制代码
这段代码会报错invalid operation: num + rVal (mismatched types int and reflect.Value)
很明显,rVal
是属于reflect.Value
类型,不能与int
类型相加
那怎样才能得到它自己的值呢?
若是是基本类型,好比var num int
,那么使用reflect
包里提供的转换方法便可reflect.ValueOf(num).Int()
或者是float
,那就调用reflect.ValueOf(num).float()
,若是是其它的基本类型,须要的时候去文档里面找找便可
但若是是咱们本身定义的结构体,由于reflect
包没法肯定咱们本身定义了什么结构体,因此自己并不会带有结构体转换的方法,那么咱们只能经过类型断言来进行转换
也就是上面说的,利用空接口进行中转,再利用断言进行类型转换,能够看以下代码示例
// Employee 员工
type Employee struct {
Name string
Age int
}
func main() {
emp := &Employee{
Name: "naonao",
Age: 99,
}
reflectPrint(emp)
}
func reflectPrint(v interface{}) {
rVal := reflect.ValueOf(v) // 获取reflect.Value
iV := rVal.Interface() // 利用空接口进行中转
empVal, ok := iV.(*Employee) // 利用断言转换
if ok {
// 若是成功转换则打印结构体
log.Print(empVal)
}
}
复制代码
这里我只是进行了一个简单的判断,若是想要进行完整的判断,仍是须要借助swith
语句,下篇会提到。也能够参照reflect
包的单元测试文件
先来看看代码如何实现
func main() {
var num = 1
modifyValue(&num)// 传递地址
log.Printf("num is %v", num)// num is 20
}
func modifyValue(i interface{}) {
rVal := reflect.ValueOf(i)
rVal.Elem().SetInt(20)
}
复制代码
细心的你确定发现了一点异常,函数接收的参数再也不是值了,而是接受了一个指针地址
改变值的时候,先调用了Elem()
方法,再进行了一个SetInt()
的操做
为何直接传值不行呢?由于reflect
包中提供的全部修改变量值的方法,都是对指针进行的操做
那为何还要先使用Elem()
呢?由于Elem()
的做用,就是取得指针地址所对应的值,取到值了,咱们才能对值进行修改
总不可能连值都没拿到手,就想着去改值吧?
关于Elem()
的使用能够简单的理解为
num := 1
prt *int := &num // 获取num的指针地址
num2 := *ptr // 从指针处取值
复制代码
由于咱们传递了一个地址,因此咱们要先拿到这个地址的指针,再经过指针去取得所对应的值
reflect
包底层实现就是基于这个原理,不过它的底层代码加了较多的判断,用来保证稳定性
这篇先说些基础概念,下篇咱们再从实践出发,看看在什么地方须要使用反射,又该如何使用reflect
包提供的方法去实现