Go 的 interface 定义了一批函数签名。interface 的字面值是:
interface {
Abs() float64
}
使用 type
给它一个名字:
type Abser interface {
Abs() float64
}
一个具体的 interface 类型的变量,需要实现 interface 要求的接口:
package main
import (
"fmt"
"math"
)
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat implements Abser
a = &v // a *Vertex implements Abser
// In the following line, v is a Vertex (not *Vertex)
// and does NOT implement Abser.
a = v
fmt.Println(a.Abs())
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
Go 的 interface 不需要显示的指定。不需要说像 Java 一样 Class A implement I。只要 struct 中实现了 interface 要求的接口,struct 的对象即可以赋值给 interface 变量。
Interface 变量的内部实现,可以认为是保存了 (value, type)
:
type I interface {
say()
}
type S struct {
}
func (s *S) say() {
if s == nil {
fmt.Println("<nil>")
} else {
fmt.Println("hello")
}
}
func main() {
var s1 *S
var i1 I = s1 // (value, type) => (nil, S)
s2 := S{}
var i2 I = &s2 // (value, type) => (&s2, S)
var i3 I // (value, type) => (nil, nil)
// Go 允许调用 struct pointer 的方法,即使 pointer 值为 nil
i1.say() // <nil>
s1.say() // <nil>
// 正常使用
i2.say() // hello
// 此时 i3 为 nil,也没有关联的 struct 类型,所以无法调用
i3.say() // runtime error
}
Go 允许你在 interface 中不指定任意函数签名,使其为 空 interface:
interface{}
空 interface 可以用来容纳任意类型的变量,比如:
func main() {
var i interface{}
describe(i) // (<nil>, <nil>)
i = 42
describe(i) // (42, int)
i = "hello"
describe(i) // (hello, string)
}
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}
判断一个 interface 的值是什么类型,可以用:
t := i.(T)
t, ok := i.(T)
上代码很容易理解:
var i interface{} = "hello"
s := i.(string)
fmt.Println(s) // hello
s, ok := i.(string)
fmt.Println(s, ok) // hello true
f, ok := i.(float64)
fmt.Println(f, ok) // 0 false
f = i.(float64) // panic
fmt.Println(f)
也可以用 type switch 来判断类型:
switch v := i.(type) {
case T:
// here v has type T
case S:
// here v has type S
default:
// no match; here v has the same type as i
}
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
留意这里的赋值操作 v := i.(type)
。假如不引入 v
,直接 switch i.(type)
,那么 case
里面的写法会很啰嗦:
func do(i interface{}) {
switch i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", i.(int), i.(int)*2)
case string:
fmt.Printf("%q is %v bytes long\n", i.(string), len(i.(string)))
default:
fmt.Printf("I don't know about type %T!\n", i)
}
}
有个典型的使用场景是 fmt.Stringer
。它定义了一个函数签名(func String() string
),实现了这个签名的类型可以被 fmt
库在打印时调用(类似于 Python 的 __str__
):
type IPAddr [4]byte
//String() 实现了,当 fmt 做打印时,IPAddr 的值是以 x.x.x.x 形式输出的
func (ip IPAddr) String() string {
return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
}
func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}
loopback: 127.0.0.1
googleDNS: 8.8.8.8
要注意的是,String()
函数的 receiver 不能是指针(即不能是 *IPAddr
)。这篇 文章 说明了为什么。