探究 Go 的高级特性之【对象比较】

981 阅读4分钟

如何比较两个go对象完全相同

在go语言中,要比较两个对象是否完全相同,我们可以使用以下三种方法:

方法一:使用reflect.DeepEqual

reflect.DeepEqual是go语言内置的深度比较函数,它可以递归比较任意类型的值,包括结构体、切片、map等。


import (

    "reflect"

)

func main() {

    a := []int{1, 2, 3}

    b := []int{1, 2, 3}

    equal := reflect.DeepEqual(a, b)

    fmt.Println(equal) // true

}

但需要注意的是,使用reflect.DeepEqual比较struct类型时,必须保证结构体内的字段顺序和类型完全相同。否则比较结果会是不正确的。以下是一个错误的例子:

import (

    "reflect"

)

 type person struct {

    Name string

    Age int

}


func main() {

    a := person{Name: "Alice", Age: 18}

    b := person{Age: 18, Name: "Alice"}

    equal := reflect.DeepEqual(a, b)

    fmt.Println(equal) // false

}

上述例子中,结构体a和b的字段顺序不同,导致比较结果为false。

方法二:使用json.Marshal进行序列化

我们可以使用json.Marshal将两个对象序列化成JSON字符串,然后比较两个字符串是否相等,以判断两个对象是否完全相同。


import (

    "encoding/json"

)


type person struct {

    Name string

    Age int

}


func main() {

    a := person{Name: "Alice", Age: 18}

    b := person{Name: "Alice", Age: 18}


    aa, _ := json.Marshal(a)

    bb, _ := json.Marshal(b)


    equal := string(aa) == string(bb)

    fmt.Println(equal) // true

}

需要注意的是,使用此方法比较struct类型时,struct内的字段类型必须是json支持的类型,否则无法进行序列化比较。同时,使用此方法比较效率相对较低。

方法三:递归比较

我们可以直接编写递归函数比较两个对象是否完全相同,这样可以保证对各种类型的支持,比较灵活,效率也比较高。

以下是一个递归比较的例子:


import (

    "reflect"

)


func IsEqual(a, b interface{}) bool {

    if reflect.DeepEqual(a, b) {

        return true

    }

    va, vb := reflect.ValueOf(a), reflect.ValueOf(b)

    if va.Kind() != vb.Kind() {

        return false

    }

    switch va.Kind() {

    case reflect.Ptr:

        return IsEqual(va.Elem().Interface(), vb.Elem().Interface())

    case reflect.Array, reflect.Slice:

        if va.Len() != vb.Len() {

            return false

        }

        for i := 0; i < va.Len(); i++ {

            if !IsEqual(va.Index(i).Interface(), vb.Index(i).Interface()) {

                return false

            }

        }

        return true

    case reflect.Map:

        if va.Len() != vb.Len() {

            return false

        }

        for _, key := range va.MapKeys() {

            if !IsEqual(va.MapIndex(key).Interface(), vb.MapIndex(key).Interface()) {

                return false

            }

        }

        return true

    case reflect.Struct:

        for i := 0; i < va.NumField(); i++ {

            if !IsEqual(va.Field(i).Interface(), vb.Field(i).Interface()) {

                return false

            }

        }

        return true

    default:

        return false

    }

}

  


type person struct {

    Name string

    Age int

}

  


func main() {

    a := []person{{"Alice", 18}, {"Bob", 20}}

    b := []person{{"Alice", 18}, {"Bob", 20}}

    equal := IsEqual(a, b)

    fmt.Println(equal) // true

}

递归比较函数中,对各种类型的判断和比较方式不同,需要根据实际情况进行编写。使用此方法时,请保证递归函数的正确性,否则会导致比较结果不正确。

项目中的使用例子

在实际项目中,可能需要比较一些复杂的对象是否完全相同。例如,在一个电商系统中,可能需要比较两个订单是否完全相同。以下是一个订单比较的代码示例:


type order struct {

    ID string

    Items []item

    Account string

    Price float64

}

  


type item struct {

    Name string

    Price float64

    Count int

}

  


func (o *order) Equal(other *order) bool {

    if o == other {

        return true

    }

  


    if o.ID != other.ID || o.Account != other.Account || o.Price != other.Price {

        return false

    }

  


    if len(o.Items) != len(other.Items) {

        return false

    }

  


    for i := range o.Items {

        if !o.Items[i].Equal(&other.Items[i]) {

            return false

        }

    }

  


    return true

}

  


func (i *item) Equal(other *item) bool {

    return i.Name == other.Name && i.Price == other.Price && i.Count == other.Count

}

在上述代码中,我们定义了一个Equal方法,在其中分别比较订单ID、账号、价格以及商品列表中每一项商品是否相同。

开源项目例子

开源项目中经常会涉及到比较对象是否完全相同的问题。以下是一些比较流行的开源项目中的比较方法:

Kubernetes

Kubernetes中定义了ObjectMeta结构体,其中包含Name、Namespace、Labels等等字段。


type ObjectMeta struct {

    Name string `json:"name,omitempty"`

    Namespace string `json:"namespace,omitempty"`

    Labels map[string]string `json:"labels,omitempty"`

    Annotations map[string]string `json:"annotations,omitempty"`

}

  


为了比较两个对象是否相同,Kubernetes中重载了Equal方法,代码如下:

  


```go

func (a *ObjectMeta) Equal(b *ObjectMeta) bool {

    if a == nil && b == nil {

        return true

    }

    if a == nil || b == nil {

        return false

    }

    return a.Namespace == b.Namespace && a.Name == b.Name && labels.Equals(a.Labels, b.Labels) && annotations.Equals(a.Annotations, b.Annotations)

}

在Equal方法中,判断两个对象的所有字段是否相同。

Etcd

Etcd是一个分布式键值存储系统,用于共享配置和服务发现。在Etcd中,定义了pb.Compare结构体,用于比较两个值是否相等。


type Compare struct {

    Target Operand

    Result Result

    Order  Comparison

}

  


type Operand interface {

    Descriptor() ([]byte, []int)

}

  


type Result interface {

    Descriptor() ([]byte, []int)

}

  


type Comparison int32

  


const (

    Comparison_EQUAL    Comparison = 0

    Comparison_GREATER  Comparison = 1

    Comparison_LESS     Comparison = 2

    Comparison_GREATER_EQUAL Comparison = 3

    Comparison_LESS_EQUAL    Comparison = 4

)

在Compare结构体中,我们可以看到三个字段,分别表示要比较的值、比较结果和比较方式。在比较方式相同时,只有要比较的值和比较结果完全相同,才能认为两个对象完全相同。

总结

我们可以使用reflect.DeepEqual、json.Marshal和递归比较等多种方式比较两个对象是否完全相同。同时,在实际项目和开源项目中,也需要根据实际情况编写相应的比较方法,保证代码的正确性和可读性。