Golang: Language: Interface

 1st November 2021 at 5:12pm

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)。这篇 文章 说明了为什么。