今天网上刷到一个网友的提问:在 Go 语言中两个 interface{} 可以比较吗?我想了一下,在我的项目中,几乎很少去直接比较两个interface{}类型的变量,但真要比较的话,答案是肯定的,两个interface{}肯定可以比较,但是多少得注意一下细节。随后,我就在网上查阅了相关资料,在这里和大家详细分享一下。

interface{}不仅仅用来表示接口,它是一个动态类型,可以用来表示任意类型,也有一个别名any。这里所说的比较是指用==!=比较。

在官网Comparison operators中有这么一句话:

Interface types that are not type parameters are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil.

意思就是非类型参数的接口类型是可比较的。如果两个接口值具有相同的动态类型和相等的动态值,或者两者都为 nil,则它们相等。

非类型参数接口:指传统的、仅包含方法签名的接口(如error),其值在运行时由动态类型动态值构成,满足条件时可比较。

类型参数接口:指泛型中作为约束的接口(如 constraints.Ordered ),属于编译时概念,本身不可比较,仅在实例化后由具体类型支持比较操作。

相同动态类型的比较

相同的动态类型是指两个接口类型的底层类型是相同的,比如两个int或两个string类型之间的比较:

package main

import (
    "fmt"
)

func main() {
    var a interface{} = 1
    var b interface{} = 1
    var c interface{} = 2
    fmt.Println(a == b) // output: true
    fmt.Println(a == c) // output: false
}

两个接口类型的底层动态类型相同是可以进行比较的,就是按它们的值比较是否相等。

如果有变量为nil的比较:

package main

import (
    "fmt"
)

func main() {
    var a interface{}
    var b interface{}
    var c interface{} = 1
    fmt.Println(a, b, c) // output: <nil> <nil> 1
    fmt.Println(a == b) // output: true 
    fmt.Println(a == c) // output: false
}

通过上面的结果可以看出,如果两个接口都为nil,则它们相等。

不同动态类型的比较

不同动态类型之间是如何比较的呢?比如一个int类型的接口和一个string类型的接口:

package main

import (
    "fmt"
)

func main() {
    var a interface{} = 1  // int
    var b interface{} = "1" // string
    var c interface{} = true // bool
    fmt.Println(a == b) // output: false
    fmt.Println(a == c) // output: false
    fmt.Println(b == c) // output: false
}

如果两个接口类型的底层动态类型不同,那它们进行比较的结果就是不相等。

动态类型为数组的比较

这里说的是数组,不是切片,数组拥有固定长度,一旦定义长度不可改变。

package main

import "fmt"

func main() {
    var a interface{} = [2]int{1, 2}
    var b interface{} = [2]int{1, 2}
    var c interface{} = [3]int{1, 2, 3}
    var d interface{} = [2]string{"1", "2"}

    fmt.Println(a == b) // output: true
    fmt.Println(a == c) // output: false
    fmt.Println(a == d) // output: false
}

可以看出,两个接口类型的数组是可以比较的,如果它们的元素类型相同且元素的值相等,那么它们就相等。

interface{}和具体类型的比较

如果一个类型为接口,另一个为具体的 int 或 string 类型,是否可以进行比较呢?也是可以比较的:

package main

import "fmt"

func main() {
    var a interface{} = 1
    var b int = 1
    var c string = ""

    fmt.Println(a == b) // output: true
    fmt.Println(a == c) // output: false
}

不可比较的类型

在 Go 语言中还有一些类型是不可比较的,比如:slice map func

package main

import "fmt"

func main() {
    var a interface{} = []int{1, 2}
    var b interface{} = []int{1, 2}

    fmt.Println(a == b)
}

// output:
panic: runtime error: comparing uncomparable type []int

goroutine 1 [running]:
main.main()
        /Users/next/Projects/godemo/main.go:9 +0x68
exit status 2

上面直接比较了两个相同的切片类型的接口,结果直接panic,所以如果要用==去比较两个interface{}一定要小心了,如果真要比较这种类型,那就用断言去判断类型或者使用reflect.DeepEqual比较。

可比较类型 comparable

在 Go 语言中引入了一个可比较类型comparable,它的注释是这样写的:

// comparable is an interface that is implemented by all comparable types
// (booleans, numbers, strings, pointers, channels, arrays of comparable types,
// structs whose fields are all comparable types).
// The comparable interface may only be used as a type parameter constraint,
// not as the type of a variable.
type comparable interface{ comparable }

通过这个注释,可以看出在 Go 语言中可比较的类型如下:

  • 基本类型 booleans, numbers, strings, pointers, channels
  • 可比较类型的数组
  • 字段全为可比较类型的结构体

那么,除此以外的类型都是不可直接比较的。

最后

在我看来,在项目中尽量不要用== !=去直接比较两个interface{}类型,一不小心panic就是个不定时炸弹。如果真要比较两个interface{},可以先断言再具体比较,实在嫌麻烦就用reflect.DeepEqual比较。