Go cmp:如何为以结构体为键的Map定义自定义相等性?
问题描述
现有如下Go代码,定义了包含指针字段的MapKey结构体作为map的键:
type Dog struct { Name string } type Cat struct { Name string } type MapKey struct { dog *Dog cat *Cat } myMap := make(map[MapKey]int)
创建两个mapgot和want,其中的键keyOne和keyTwo是不同的MapKey实例,但它们指向的Dog、Cat结构体值完全相同:
keyOne := MapKey{ &Dog{Name: "bob"}, &Cat{Name: "clive"}, } keyTwo := MapKey{ &Dog{Name: "bob"}, &Cat{Name: "clive"}, } got := map[MapKey]int{ keyOne: 1, } want := map[MapKey]int{ keyTwo: 1, }
希望使用cmp包判定这两个map相等,但默认的cmp.Equal会认为两个map不相等——因为它默认比较指针的地址而非指向的结构体内容。尝试为MapKey定义自定义Equals函数未生效,也未理解cmpopts.SortMaps与当前场景的关联。
解决方案
方法1:使用cmp.Comparer自定义键的比较逻辑
cmp.Comparer允许你为特定类型定义自定义的相等判定规则。针对MapKey,我们可以编写比较器,忽略指针地址,转而比较指针指向的结构体内容:
package main import ( "fmt" "github.com/google/go-cmp/cmp" ) type Dog struct { Name string } type Cat struct { Name string } type MapKey struct { dog *Dog cat *Cat } func main() { keyOne := MapKey{ &Dog{Name: "bob"}, &Cat{Name: "clive"}, } keyTwo := MapKey{ &Dog{Name: "bob"}, &Cat{Name: "clive"}, } got := map[MapKey]int{keyOne: 1} want := map[MapKey]int{keyTwo: 1} // 定义MapKey的自定义比较器 mapKeyComparer := cmp.Comparer(func(a, b MapKey) bool { // 处理指针为nil的情况,再比较结构体内容 dogEqual := (a.dog == nil && b.dog == nil) || (a.dog != nil && b.dog != nil && a.dog.Name == b.dog.Name) catEqual := (a.cat == nil && b.cat == nil) || (a.cat != nil && b.cat != nil && a.cat.Name == b.cat.Name) return dogEqual && catEqual }) // 使用自定义比较器执行相等判定 if cmp.Equal(got, want, mapKeyComparer) { fmt.Println("Maps are equal") } else { fmt.Println("Maps are not equal") } }
方法2:让MapKey实现cmp.Equaler接口
如果希望MapKey类型默认使用自定义相等逻辑,可以让它实现cmp.Equaler接口(注意接口要求的参数类型是interface{}):
package main import ( "fmt" "github.com/google/go-cmp/cmp" ) type Dog struct { Name string } type Cat struct { Name string } type MapKey struct { dog *Dog cat *Cat } // 实现cmp.Equaler接口 func (mk MapKey) Equal(x interface{}) bool { other, ok := x.(MapKey) if !ok { return false } dogEqual := (mk.dog == nil && other.dog == nil) || (mk.dog != nil && other.dog != nil && mk.dog.Name == other.dog.Name) catEqual := (mk.cat == nil && other.cat == nil) || (mk.cat != nil && other.cat != nil && mk.cat.Name == other.cat.Name) return dogEqual && catEqual } func main() { keyOne := MapKey{ &Dog{Name: "bob"}, &Cat{Name: "clive"}, } keyTwo := MapKey{ &Dog{Name: "bob"}, &Cat{Name: "clive"}, } got := map[MapKey]int{keyOne: 1} want := map[MapKey]int{keyTwo: 1} // 直接调用cmp.Equal,会自动使用MapKey的Equal方法 if cmp.Equal(got, want) { fmt.Println("Maps are equal") } else { fmt.Println("Maps are not equal") } }
补充说明
cmpopts.SortMaps的作用是比较map时忽略键的顺序,和当前场景无关——你的问题是键被判定为不同,而非键的顺序导致的不相等。- 之前自定义的
Equals函数未生效,是因为cmp包不会自动识别任意命名的相等方法,必须通过cmp.Comparer选项,或者实现cmp.Equaler标准接口。
内容的提问来源于stack exchange,提问作者Charlie Clarke
相关产品推荐
相关产品推荐

