面试官:请问 Golang 中 interface 可以比较吗?

面试回答

可以比较,但有条件,并且存在触发 panic 的风险。

在 Go 语言中,interface 的比较主要分为以下几种情况:

  1. nil 比较:只有当 interface 的动态类型(type)和动态值(value)都为 nil 时,它才等于 nil
  2. 两个 interface 互相比较
    • 如果两个 interface 的动态类型不同,则它们不相等。
    • 如果两个 interface 的动态类型相同,并且该类型是可比较的(如基础类型、指针等),则比较它们的动态值。
    • 致命陷阱:如果两个 interface 的动态类型相同,但这个类型本身是不可比较的(比如 slicemapfunc),在使用 == 进行比较时,会在运行时引发 panic

因此,在不确定 interface 底层动态类型是否可比较时,直接使用 == 是不安全的。

系统讲解

1. Interface 的底层结构

要理解 interface 的比较逻辑,首先需要知道它在 Go 运行时的底层表示。一个空接口 interface{}(即 eface)在底层的结构大致如下:

type eface struct {
    _type *_type         // 动态类型
    data  unsafe.Pointer // 动态值
}

当使用 == 比较两个 interface 变量时,Go 语言会按照以下顺序进行判断:

  1. 先比较 _type(动态类型)。如果类型不同,直接返回 false
  2. 如果 _type 相同,再检查该类型是否实现了比较操作(即是否为可比较类型)。
  3. 如果是可比较类型,则进一步比较 data(动态值)是否相等。
  4. 如果是不可比较类型,直接抛出 panic。

2. 比较规则总结表

比较情况结果说明
都为 niltrue两个未初始化的 interface,其 type 和 data 均为 nil。
动态类型不同false即使底层数据的值看起来一样(例如 int(1)int64(1)),类型不同即为 false。
动态类型相同,且为可比较类型比较动态值intstringstruct(字段均可比较)、指针等。值相等则为 true。
动态类型相同,但为不可比较类型Panicslicemapfunc。运行时会抛出 panic: comparing uncomparable type...

3. 代码示例

package main
 
import "fmt"
 
func main() {
    // 1. 都为 nil
    var a, b interface{}
    fmt.Println(a == b) // true
 
    // 2. 动态类型不同
    var c interface{} = 1     // type: int
    var d interface{} = "1"   // type: string
    fmt.Println(c == d) // false
 
    // 3. 动态类型相同,且可比较
    var e interface{} = 100
    var f interface{} = 100
    fmt.Println(e == f) // true
 
    // 4. 动态类型相同,但不可比较 (触发 panic)
    var g interface{} = []int{1, 2, 3}
    var h interface{} = []int{1, 2, 3}
    // fmt.Println(g == h) // 取消注释会引发 panic: comparing uncomparable type []int
}

4. 常见追问

追问:如何安全地比较两个可能包含不可比较类型的 interface?

如果无法确定 interface 的底层类型,或者明确需要比较包含 slicemap 的复杂结构,应该使用反射包中的 reflect.DeepEqual() 函数。

reflect.DeepEqual() 会递归地比较两个变量的值。对于 slicemap,它会逐个比较其中的元素;如果遇到类型不匹配或值不相等,它会安全地返回 false,而不会引发 panic。

package main
 
import (
    "fmt"
    "reflect"
)
 
func main() {
    var g interface{} = []int{1, 2, 3}
    var h interface{} = []int{1, 2, 3}
    
    // 使用 reflect.DeepEqual 安全比较
    fmt.Println(reflect.DeepEqual(g, h)) // 输出: true
}