Golang: Language: Interface

27th April 2021 at 7:10am
Golang: Language

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)
    }
}

有个典型的使用场景是 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)。这篇 文章 说明了为什么。