接口值陷阱

概念

接口值共有两部分组成:具体的类型、该类型的值。它们称之为接口值的动态类型和动态值。

接口值判断陷阱

既然接口值有两部分组成,那么,接口值的相等性判断是否也要类型及值均相等才算相等呢?

答案是肯定的,以下两个示例:

1
2
3
4
5
6
// 公共部分
type People interface { }
type Student struct { }
type Teacher struct { }
// 均无方法,方便测试
// 可以看作Student Teacher都实现了 People接口
1
2
3
4
5
6
7
8
9
10
// 示例1

var p1, p2 People
var s *Student
var t *Teacher
fmt.Println(s == nil, t == nil) // true true
p1 = s
p2 = t
fmt.Println(p1 == p2) // false
// p1 p2 值均为空,但其类型不同,因此判断相等结果为false
1
2
3
4
5
6
7
8
9
10
// 示例2

var p1 People
var s *Student
fmt.Println(p1 == nil) // true
fmt.Println(s == nil) // true
p1 = s
fmt.Println(p1 == nil) // false
// p1 值为nil,但类型不为nil,所以p1与nil做相等比较结果false
// 此处要注意,在函数中用接口类型做为形参时,如果直接将其与nil做比较来判断其是否为空,可能会有意想不到的结果,且不易排查

接口不同实现方式

  1. 使用指针接收者实现
  2. 使用类型接收者实现

指针接收者实现

使用指针接收者实现,则只有该实体类型的指针实现了该接口。
不能将实体类型的值赋值给该接口类型变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

type People interface {
SayHello()
}
type Student struct {
}

func (*Student) SayHello() {
fmt.Println("hello")
}
func main() {
var p People
// p = Student{} // 编译报错
p = &Student{}
p.SayHello()
}

类型接收者实现

能够将实体类型和实体类型的指针赋值给接口类型变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

type People interface {
SayHello()
}
type Student struct {
}

func (Student) SayHello() {
fmt.Println("hello")
}
func main() {
var p People
p = Student{}
p.SayHello()
p = &Student{}
p.SayHello()
}