Interface

Interface 的定义

  • Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
  • 接口可以让我们将不同的类型绑定到一组公共的方法上,从而实现多态和灵活的设计。
  • Go 语言中的接口是隐式实现的,也就是说,如果一个类型实现了一个接口定义的所有方法,那么它就自动地实现了该接口。因此,我们可以通过将接口作为参数来实现对不同类型的调用,从而实现多态。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 定义接口 */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
   /* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* 方法实现*/
}

以下两个实例演示了接口的使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
    "fmt"
)

type Phone interface {
    call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}

func (iPhone IPhone) call() {
    fmt.Println("I am iPhone, I can call you!")
}

func main() {
    var phone Phone

    phone = new(NokiaPhone)
    phone.call()

    phone = new(IPhone)
    phone.call()

}

在上面的例子中,我们定义了一个接口 Phone,接口里面有一个方法 call()。然后我们在 main 函数里面定义了一个 Phone 类型变量,并分别为之赋值为 NokiaPhone 和 IPhone。然后调用 call() 方法,输出结果如下:

1
2
I am Nokia, I can call you!
I am iPhone, I can call you!

第二个接口实例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import "fmt"

type Shape interface {
    area() float64
}

type Rectangle struct {
    width  float64
    height float64
}

func (r Rectangle) area() float64 {
    return r.width * r.height
}

type Circle struct {
    radius float64
}

func (c Circle) area() float64 {
    return 3.14 * c.radius * c.radius
}

func main() {
    var s Shape

    s = Rectangle{width: 10, height: 5}
    fmt.Printf("矩形面积: %f\n", s.area())

    s = Circle{radius: 3}
    fmt.Printf("圆形面积: %f\n", s.area())
}

以上实例中,我们定义了一个 Shape 接口,它定义了一个方法 area(),该方法返回一个 float64 类型的面积值。然后,我们定义了两个结构体 Rectangle 和 Circle,它们分别实现了 Shape 接口的 area() 方法。在 main() 函数中,我们首先定义了一个 Shape 类型的变量 s,然后分别将 Rectangle 和 Circle 类型的实例赋值给它,并通过 area() 方法计算它们的面积并打印出来,输出结果如下:

1
2
矩形面积: 50.000000
圆形面积: 28.260000

需要注意的是,接口类型变量可以存储任何实现了该接口的类型的值。在示例中,我们将 Rectangle 和 Circle 类型的实例都赋值给了 Shape 类型的变量 s,并通过 area() 方法调用它们的面积计算方法。

类型转换

接口类型转换有两种情况:类型断言和类型转换。 类型断言用于将接口类型转换为指定类型,其语法为:

1
2
3
value.(type) 
或者 
value.(T)

其中 value 是接口类型的变量,type 或 T 是要转换成的类型。 如果类型断言成功,它将返回转换后的值和一个布尔值,表示转换是否成功。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import "fmt"

func main() {
    var i interface{} = "Hello, World"
    str, ok := i.(string)
    if ok {
        fmt.Printf("'%s' is a string\n", str)
    } else {
        fmt.Println("conversion failed")
    }
}

以上实例中,我们定义了一个接口类型变量 i,并将它赋值为字符串 “Hello, World”。然后,我们使用类型断言将 i 转换为字符串类型,并将转换后的值赋值给变量 str。最后,我们使用 ok 变量检查类型转换是否成功,如果成功,我们打印转换后的字符串;否则,我们打印转换失败的消息。 类型转换用于将一个接口类型的值转换为另一个接口类型,其语法为:

1
T(value)

T 是目标接口类型,value 是要转换的值。 在类型转换中,我们必须保证要转换的值和目标接口类型之间是兼容的,否则编译器会报错。

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

import "fmt"

type Writer interface {
    Write([]byte) (int, error)
}

type StringWriter struct {
    str string
}

func (sw *StringWriter) Write(data []byte) (int, error) {
    sw.str += string(data)
    return len(data), nil
}

func main() {
    var w Writer = &StringWriter{}
    sw := w.(*StringWriter)
    sw.str = "Hello, World"
    fmt.Println(sw.str)
}

以上实例中,我们定义了一个 Writer 接口和一个实现了该接口的结构体 StringWriter。然后,我们将 StringWriter 类型的指针赋值给 Writer 接口类型的变量 w。接着,我们使用类型转换将 w 转换为 StringWriter 类型,并将转换后的值赋值给变量 sw。最后,我们使用 sw 访问 StringWriter 结构体中的字段 str,并打印出它的值。

泛型

自 Go 1.18 支持泛型后, Go interface 的意义已经彻彻底底的改变了,除了先前代表的方法集的意义外,还被用作泛型的类型约束(type constraint)的功能, interface已经不再是以前那个单纯的少年了。 在Go 1.17.x以及以前的版本中,interface是这样定义的:

An interface type specifies a method set called its interface. A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil.

接口类型定义了一个方法集合,称之为接口(interface)。接口类型的变量可以存储任意的实现这个方法集合的类型,这种类型是此interface的超集。这种类型被称为实现了接口。接口类型的变量如果未初始化则它的值为nil。

在Go 1.18中,interface定义改变了:

An interface type defines a type set. A variable of interface type can store a value of any type that is in the type set of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil.

接口类型定义了一个类型集合。接口类型的变量可以存储这个接口类型集合的任意一种类型的实例值。这种类型被称之为实现了这个接口。接口类型的变量如果未初始化则它的值为nil。

所以一句话,先前接口定义了方法集合,现在接口定义了类型集合。接口的用途也进行了扩展。

interface的定义也扩展了。先前,接口定义只能包含方法元素(method element):

1
2
3
4
5
interface {
	Read([]byte) (int, error)
	Write([]byte) (int, error)
	Close() error
}

现在接口定义除了方法元素外,还可以包含类型元素:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
interface {
	int
}
// An interface representing all types with underlying type int.
interface {
	~int
}
// An interface representing all types with underlying type int which implement the String method.
interface {
	~int
	String() string
}
// An interface representing an empty type set: there is no type that is both an int and a string.
interface {
	int
	string
}

类型元素包含类型(T)或者近似类型(~T)或者联合(union)元素(A|B|C|~D)。

但是,因为接口的定义和含义改变了,所以接口在使用的时候也有一些些不同。本文通过实例一一介绍。

首先记住一点,Go 1.17.x 及以前的版本中接口的使用方法在Go 1.18中照样使用,使用方法不变。变得是接口有类型元素或者做类型约束时的一些限制。

  • 近似元素的类型T必须是底层类型(underlying type)自己,而且不能是接口类型
1
2
3
4
5
6
// 错误的定义!
type MyInt int
type I0 interface {
	~MyInt // 错误! MyInt不是underlying type, int才是
	~error // 错误! error是接口
}
  • 联合(union)类型元素不能是类型参数(type parameter)
1
2
3
4
5
6
7
// 错误, interface{ K }中K是类型参数
func I1[K any, V interface{ K }]() {
}

// 错误, interface{ K }中K是类型参数
func I1[K any, V interface{ K }]() {
}
  • 联合(union)类型元素的非接口元素必须是两两不相交

    两两不相交意思是两两的交集是空集,比如 int|string的交集是空集,而int|~int的交集是int。

    联合类型中的非接口元素必须是两两不相交的。

    下面的定义没问题,因为any等价于interface{}:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func I3[K any, V interface{ int | any }]() {
}

// 错误! int和!int相交
func I4[K any, V interface{ int | ~int }]() {
}
// 下面的定义没有问题。因为int和MyInt是两个类型,不相交
type MyInt int
func I5[K any, V interface{ int | MyInt }]() {
}
// 错误! int和~MyInt相交,交集是int
func I6[K any, V interface{ int | ~MyInt }]() {
}
// 错误! int和MyInt2是相同类型,相交
type MyInt2 = int
func I7[K any, V interface{ int | MyInt2 }]() {
}
  • 联合(union)类型元素如果包含多于一个元素,不能包含包含非空方法的接口类型,也不能是comparable或者嵌入comparable 这条规则定义了接口作为类型元素的一些限制.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 编译没问题,只包含一个元素
func I9[K interface{ io.Reader }]() {
}
// 错误!不能编译。因为包含了两个元素,而且无论是`io.Reader`还是`io.Writer`都包含方法
func I10[K interface{ io.Reader | io.Writer }]() {
}
// 编译正常,因为这是正常的接口,没有联合元素
func I11[K interface {
	io.Reader
	io.Writer
}]() {
}
// 错误! 联合类型多于一个元素,并且io.Reader包含方法
func I12[K interface{ io.Reader | int }]() {
}
// 错误! 不能编译.因为联合元素大于一个,并且不能是comparable
func I13[K comparable | int]() {
}
// 错误! 不能编译.因为联合元素大于一个,并且元素不能嵌入comparable
func I14[K interface{ comparable } | int]() {
}
  • 包含非接口类型元素、近似元素和联合类型只能用作类型参数,或者其它用作约束接口的元素
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var (
    // 以下编译没问题 
	_ interface{}
	_ interface{ m() }
	_ interface{ io.Reader }
	_ interface {
		io.Reader
		io.Writer
	}
    // 以下不能编译,接口不能用作变量实例类型
	_ interface{ int }
	_ interface{ ~int }
	_ interface{ MyInt }
	A interface {
   	  int
	  m()
	}
    // 可以编译
	_ struct{ i int }
    // 下面一行不能编译,因为~int不能作为字段的类型
	_ struct{ i ~int }
    // 下面一行不能编译,因为constraints.Ordered只能用作类型约束
	_ struct{ i constraints.Ordered }
    // 下面两行能够编译,因为它们是接口类型,并且类型元素也是普通接口
    _ interface{ any }
	_ interface {
		interface {
			any
			m()
		}
	}
    // 不能编译,因为接口部署普通接口,而是类型约束
	_ interface {
		interface {
			int|~int
			m()
		}
	}
)
  • 接口类型不定递归嵌入
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 错误! 不能自己嵌入自己
type Node interface {
	Node
}
// 错误! Tree不能通过TreeNode嵌入自己
type Tree interface {
	TreeNode
}
type TreeNode interface {
	Tree
}