概要
在Go没有泛型时,支持反射是Go的一大杀手锏能力。本篇不是为了研究reflect包的实现原理,而是想通过实践学习如何使用Go的reflect包。
reflect包
基本概念
reflect 有2个重要的类型,和一个Kind类型:
- reflect.Type:是一个interface
- reflect.Value:是一个struct
- reflect.Kind:从Type.Kind()获取,本质是一个uint。例如,同样是struct,struct A和struct B他们的Kind应该不一样
字面理解,他们分别表示对象的类型和值相关的封装。
那么Type都有些什么呢,我们往往通过.Kind()获取Kind区分。对于内建类型,reflect已经定义了他们的Kind,如reflect.Int。
除此之外,使用reflect包要注意指针类型和值类型是不一样的,指针类型的Type是reflect.Ptr,通过.Elem()获取指针指向的对象的Value。类似的,切片,数组,map等都有自己Type。
常用方法
func fun() {
// 获取变量的Type
type1 := reflect.TypeOf(var1)
// 获取变量的Value
value1 := reflect.ValueOf(var1)
// 从Value获取Type
type11 := value.Type()
// 获取Type对应的Kind
kind := type1.Kind()
// 获取Value的真正值,并转化为interface{}
interface1 := value1.Interface()
// 对于指针Type
isNil := value1.IsNil()
valuePointTo := value1.Elem()
// 对于struct Type
for i := 0; i < type1.NumField(); i++ {
valueF1 := value1.Filed(i)
filedName := type1.Field(i).Name
}
// 对于slice Type
len1 := value1.Len()
valueIdx1 := value1.Index(i)
// SetXXX()
fVal.Elem().Field(0).SetInt(20)
// Indirect 对指针进行解引用
reflect.Indirect(outVal).Set(reflect.Indirect(objVal))
}
除此之外,一个非常有用的方法是 refelct.DeepEqual(if1, if2)
自己实现refelct.DeepEqual
由于反射后还要反射,要深比较两个对象,要不断地递归到基础类型,才能进行值比较,所以我们只实现一个简单的深比较,暂且叫做SimpleEqual。
func SimpleEqual(x, y interface{}) bool {
if x == nil || y == nil {
return x == y
}
v1 := ValueOf(x)
v2 := ValueOf(y)
if v1.Type() != v2.Type() {
return false
}
return simpleEqualValue(v1, v2)
}
func simpleEqualValue(v1, v2 reflect.Value) bool {
t1 := v1.Kind()
switch t1 {
case reflect.Bool:
fallthrough
// 此处省略若干基础值类型
case reflect.Int:
return v1.Interface() == v2.Interface()
}
case reflect.Ptr:
if !v1.IsNil() && !v2.IsNil() {
return simpleEqualValue(v1.Elem(), v2.Elem())
}
// ...
case reflect.Struct:
for i := 0; i < v1.NumField(); i++ {
if !simpleEqualValue(v1.Field(i), v2.Field(i)) {
return false
}
}
return true
// 省略其他 Kind
}
可见,实现一个真正可用的 DeepEqual 还是很复杂的。reflect包还有很多其他有用的方法,例如typeA.Field(i).Tag 可以获取结构体字段对应的Tag。
小心!
reflect包的绝大部分方法都可能引发panic,没有我们常见的返回error类型,使用时要非常小心。