interface能否用作map的key

很久之前面试时,有面试官表示,interface、指针不能用于map的key,当时不解,interface确实不太清楚,不过指针不就是uint64变量吗,难道uint64不能作为key吗?今天忽然回想起来,决定搞搞清楚。

官方说法

根据Andrew Gerrand在2013年的Go maps in action【1】一文中,对于Key type进行了明确说明:

As mentioned earlier, map keys may be of any type that is comparable. The language spec defines this precisely, but in short, comparable types are boolean, numeric, string, pointer, channel, and interface types, and structs or arrays that contain only those types. Notably absent from the list are slices, maps, and functions; these types cannot be compared using ==, and may not be used as map keys.

在这篇博文中,指出了可以作为key的类型范围,Andrew指出,map的key需要是可以比较的类型,而pointer和interface类型(注意,不是标准库sort包的Interface类型)是可比较类型,所以这两种类型是可以作为map的key的。

在go语言标准中【2】,对可比较类型的比较方式进行了定义,即什么情况下,定义为类型量之间相等。除了常规的内置类型外,这里有几个特别的类型值得一看:

  • 对于chan:同一个make创建的chan,都是相等的,这意味着将chan传参到函数中,与原始chan是可以比较的,且他们相等。
  • 对于pointer:指向目标相同即相等,其实这里隐含了类型相同和值相同,类型就是*T中的T,值就是指针指向的内存地址。此外,指向同一个大小为0的结构体/数组的指针,未必相同。
  • 对于struct:要求struct的所有成员是可比较类型,则struct为可比较类型。
  • 对于interface:要求其隐含的类型相同、其类型所表达的值相同,则两个interface类型量相等。或者两个interface类型变量都是nil,可以理解为他们都包含空类型信息和空值。

实践

interface是包含类型信息的,这是容易被忽视的一点,由于map需要依赖key和key的比较结果,来决定是否是同一个hash映射,所以下面通过一些例子,看看interface类型在比较中的表现


package main

import (
	"fmt"
)

type A struct {
}
type B struct {
}

func main() {
	// 值相同、类型不同
	var a *A
	var b *B

	var ia, ib interface{}
	ia = a
	ib = b

	if ia == ib {
		fmt.Println("ia equal ib")
	} else {
		fmt.Println("ia not equal ib") // 被打印
	}

	// 类型相同、值不同
	var c, d *A
	d = new(A)
	var ic, id interface{}
	ic = c
	id = d

	if ic == id {
		fmt.Println("ic equal id")
	} else {
		fmt.Println("ic not equal id") // 被打印
	}

	// 类型与值都相同,注意这里的值是指针
	var e *A
	var ie, ig interface{}
	ie = e
	ig = e

	if ie == ig {
		fmt.Println("ie equal ig") // 被打印
	} else {
		fmt.Println("ie not equal ig")
	}

	// 类型与值都相同,这里是使用的值,非指针
	var m, n A
	var im, in interface{}
	im = m
	in = n

	if im == in {
		fmt.Println("im equal in") // 被打印
	} else {
		fmt.Println("im not equal in")
	}

	// nil不包含类型信息,所以与隐含类型信息的interface不相等,即使指针h等于nil
	var h *A
	var ih interface{}
	ih = h
	if ih == nil {
		fmt.Println("ih equal nil")
	} else {
		fmt.Println("ih not equal nil") // 被打印
	}

	// nil不包含类型信息,所以不包含类型信息的interface相等
	var ik interface{}
	if ik == nil {
		fmt.Println("ik equal nil") // 被打印
	} else {
		fmt.Println("ik not equal nil")
	}
}

map的key在做比较时,同时依赖类型和值,所以只要类型或值有一样不同,就能区分不同的key,你需要确保你的hash表中的key在类型或值上不同。特别的,如果你的hash表中有多个类型相同但值为nil的key,那就要小心了,写hash映射时可能会丢失信息。

可能正是因为interface有这样的特性,所以会有人不建议将interface变量作为key

结论

语言层面支持interface、pointer作为map的key。事实上阅读过runtime代码就会发现,在go runtime中有很多将interface类型作为key的map(e.g. sync.Map),所以能否将interface用作key的关键在于 ,你确定知道自己在做什么。

参考

  1. Go maps in action
  2. The Go Programming Language Specification
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值