6.9. 接口类型判断

Go语言的switch不仅可以像其他语言一样实现数值、字符串的判断,还有一种特殊的用途—判断一个接口内保存或实现的类型

6.9.1. 1.类型断言的书写格式

switch 实现类型分支时写法格式如下:

switch 接口变量(type){
    case 变量1:
        //变量是类型1时的处理
    case 变量2:
        //变量是类型2时的处理
    .....
    default:
        // 变量不是所有case中列举的类型时的处理
}
switch x.(type) {
    case nil:       // ...
    case int, uint: // ...
    case bool:      // ...
    case string:    // ...
    default:        // ...
}
  • 接口变量:表示需要判断的接口类型的变量。

  • 类型1、类型2····: 表示接口变量可能具有的类型列表,满足时,会指定case对应的分支进行处理。

6.9.2. 2.使用类型分支判断基本类型

package main

import "fmt"

func printType(v interface{}) {
    switch v.(type) {
    case int:
        fmt.Println(v, "is int")        //1024 is int
    case string:
        fmt.Println(v, "is string")     //hujianli is string
    case bool:
        fmt.Println(v, "is bool")       //true is bool
    }
}

func main() {
    printType(1024)
    printType("hujianli")
    printType(true)
}

6.9.3. 3.使用类型分支判断接口类型

多个接口进行类型断言时,可以使用类型分支简化判断过程。

package main

import "fmt"

// 电子支付方式
type Alipay struct {
}

// 为Alipay添加CanUseFaceID()方法,表示电子支付方式支持刷脸
func (a *Alipay) CanUseFaceID() {
}

// 现金支付方式
type Cash struct {
}

// 为Cash添加Stolen()方法,表示现金支付方式会出现偷窃情况
func (a *Cash) Stolen() {
}

// 具备刷脸特性的接口
type CantainCanUseFaceID interface {
    CanUseFaceID()
}

// 具备被偷特性的接口
type CantainStolen interface {
    Stolen()
}

// 打印支付方式具备的特点
func print(payMethod interface{}) {
    switch payMethod.(type) {
    case CantainCanUseFaceID: // 可以刷脸
        fmt.Printf("%T can use faceid\n", payMethod)
    case CantainStolen: // 可能被窃
        fmt.Printf("%T may be stolen\n", payMethod)
    }
}

func main() {
    //使用电子支付判断
    print(new(Alipay)) //*main.Alipay can use faceid

    //使用现金判断
    print(new(Cash)) //*main.Cash may be stolen
}

6.9.4. 4.类型断言

package main

import "fmt"

type Robot string

func (r Robot) MakeSound() {
    fmt.Println("Beep beep!!")
}

func (r Robot) Walk() {
    fmt.Println("Powering legs")
}

type NoiseMaker interface {
    MakeSound()
}

func main() {
    // 定义接口类型的变量,将满足接口类型的值赋值给它
    var noiseMaker NoiseMaker = Robot("Botco Ambler")
    noiseMaker.MakeSound()
    // 类型断言,取回具体的类型
    var robot Robot = noiseMaker.(Robot)
    // 调用在具体类型上的方法,而不是接口
    robot.Walk()

}

代码将Robot赋值给了NoiseMaker接口值。

我们可以调用NoiseMaker上的MakeSound方法,因为它是接口的一部分。

但是为了调用Walk方法,我们需要使用类型断言来取回Robot值。

一旦我们获取了Robot(而不是一个NoiseMaker),我们就能调用它上面的Walk方法。

## 5.接口类型断言

一个接口的值(简称接口值)是由一个具体类型具体类型的值两部分组成的。这两部分分别称为接口的动态类型动态值

想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:

x.(T)

其中:

  • x:表示类型为interface{}的变量

  • T:表示断言x可能是的类型。

该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败。

func main() {
    var x interface{}
    x = "Hello word"
    v, ok := x.(string)
    if ok {
        fmt.Println(v)
    } else {
        fmt.Println("类型断言失败")
    }
}

上面的示例中如果要断言多次就需要写多个if判断,这个时候我们可以使用switch语句来实现:

func justifyType(x interface{}) {
    switch v := x.(type) {
    case string:
        fmt.Printf("x is a string,value is %v\n", v)
    case int:
        fmt.Printf("x is a int is %v\n", v)
    case bool:
        fmt.Printf("x is a bool is %v\n", v)
    default:
        fmt.Println("unsupport type!")
    }
}

因为空接口可以存储任意类型值的特点,所以空接口在Go语言中的使用十分广泛。

关于接口需要注意的是,只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。

不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。

package main

import "fmt"

type Truck string
func (t Truck) Brake() {
    fmt.Println("Stopping", t)
}

func (t Truck) Steer(dire string) {
    fmt.Println("Turning", dire)
}

func (t Truck) LoadCargo(cargo string) {
    fmt.Println("Loading", cargo)
}

type Truck2 string
func (t Truck2) Brake() {
    fmt.Println("Stopping", t)
}

func (t Truck2) Steer(dire string) {
    fmt.Println("Turning", dire)
}

type Vehicle interface {
    Brake()
    Steer(string)
}

func TryVehicle(vehicle Vehicle) {
    vehicle.Brake()
    vehicle.Steer("left")
    vehicle.Steer("right")
    truk, ok := vehicle.(Truck)
    if ok {
        truk.LoadCargo("test cargo")
    }
}

func main() {
    TryVehicle(Truck("hujianli aaa"))
    fmt.Println()
    TryVehicle(Truck2("hujianli aaa"))
}

总结

  1. 接口类型底层存放的是两个值,一个是类型type,一个是值value

  2. 两个接口值相等仅当它们都是nil值或者它们的动态类型相同并且动态值也相同(前提是动态值是可以比较的)

  3. 判断接口类型:v, ok := x.(string)

  4. 单纯断言判断接口值类型

    switch x.(type) {
    case *Square:
        // TODO
    case *Circle:
        // TODO
    ...
    default:
        // TODO
    }