6.7. 在接口和类型间转换

6.7.1. 1 类型断言的格式

接口类型I的变量 varI 中可以包含任何实现了这个接口的类型的值,如果多个类型都实现了这个接口,所以有时我们需要用一种动态方式来检测它的真实类型,即在运行时确定变量的实际类型。

通常我们可以使用类型断言(value, ok := element.(T))来测试在某个时刻接口变量 varI 是否包含类型 T 的值:

value, ok := varI.(T)       // 类型断言

varI 必须是一个接口变量,否则编译器会报错:invalid type assertion: varI.(T) (non-interface type (type of I) on left) 。

1.1 类型断言的例子

package main

import "fmt"

// 定义一个I接口,f()方法
type I interface {
    f()
}

type T string

// 实现接口方法
func (t T) f() {
    fmt.Println("T Meathod")
}

// 定义一个接口,一个方法,返回一个字符串
type Stringer interface {
    String() string
}

func main() {
    // 类型断言
    var varI I
    varI = T("Tstring")
    if v, ok := varI.(T); ok {
        // 类型断言
        fmt.Println("varI 类型断言结果为:", v) // varI已经转为T类型      //varI 类型断言结果为: Tstring
        varI.f()                //T Meathod
    }

    //Type-switch做类型判断
    var value interface{} // 默认为0值

    switch str := value.(type) {
    case string:
        fmt.Println("value类型的断言结果为string: ", str)
    case Stringer:
        fmt.Println("value类型断言结果为Stringer:", str)
    default:
        fmt.Println("value 类型不在上述类型之中....")         //value 类型不在上述类型之中....
    }
    // Comma-ok断言
    value = "类型断言检查"
    str, ok := value.(string)
    if ok {
        fmt.Printf("value类型断言结果为:%T\n", str) // value类型断言结果为:string
    } else {
        fmt.Printf("value不是string类型 \n")
    }

}

6.7.2. 2.将接口转换为其他接口

实现某个接口的类型同时实现了另外一个接口,此时可以在两个接口间转换。

鸟和猪具有不同的特性,鸟可以飞,猪不能飞,但两种动物都可以行走。

如果使用结构体实现鸟和猪,让它们具备自己特性的 Fly() 和 Walk() 方法就让鸟和猪各自实现了飞行动物接口(Flyer)和行走动物接口(Walker)。

将鸟和猪的实例创建后,被保存到 interface{} 类型的 map 中。

interface{} 类型表示空接口,意思就是这种接口可以保存为任意类型。

对保存有鸟或猪的实例的 interface{} 变量进行断言操作,如果断言对象是断言指定的类型,则返回转换为断言对象类型的接口;

如果不是指定的断言类型时,断言的第二个参数将返回 false。

例如下面代码

var obj interface = new(bird)
f, isFlyer := obj.(Flyer)
package main

import "fmt"

// 定义飞行动物的接口
type Falyer interface {
    Fly()
}

// 定义行走动物的接口
type Walker interface {
    Walk()
}

// 定义鸟类
type bird struct {
}

// 为鸟添加Walk()方法,实现行走动物接口。
func (b *bird) Walk() {
    fmt.Println("bird: walk")
}

// 定义猪
type pig struct {
}

func (p *pig) Walk() {
    fmt.Println("pig: walk")
}

func main() {
    // 创建动物的名字到实例的映射
    animals := map[string]interface{}{
        "bird": new(bird),
        "pig":  new(pig),
    }

    // 遍历映射
    for name, object := range animals {
        // 判断对象是否为飞行动物
        f, isFlyer := object.(Falyer)

        // 判断对象是否为行走动物
        w, isWalker := object.(Walker)

        fmt.Printf("name: %s isFlay: %v isWalker: %v\n", name, isFlyer, isWalker)
        // 如果是飞行动物则调用飞行动物接口

        //根据飞行动物和行走动物两者是否断言成功,调用其接口。
        if isFlyer {
            f.Fly()
        }

        // 如果是行走动物则调用行走动物接口
        if isWalker {
            w.Walk()
        }
    }
}

/*
name: bird isFlay: false isWalker: true
bird: walk
name: pig isFlay: false isWalker: true
pig: walk
 */

6.7.3. 3.将接口转换为其他类型

// 实例化pig类
p1 := new(pig)
// 将类关联到接口
var a Walker = p1
p2 := a.(*pig)
fmt.Printf("p1=%p p2=%p\n", p1, p2)     //p1=0x597c18 p2=0x597c18
  • 由于pig实现了Walker接口,因此可以被隐式转换为Walker接口类型,保存于a中。

  • a中保存的本来就是*pig本体,因此可以转换为*pig类型

  • p1和p2的指针是相同的。