Go的reflect包会用吗?手把手实现一个reflect.DeepEqual()

439 阅读2分钟

概要

在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类型,使用时要非常小心。

参考链接

go101.org/article/ref…