Go基础系列:接口类型断言和type-switch

接口转回成具体类型

接口实例中能够存放各类实现了接口的类型实例,在有须要的时候,还能够经过ins.(Type)ins.(*Type)的方式将接口实例ins直接转回Type类型的实例。测试

var i int = 30
var ins interface{}

// 接口实例ins中保存的是int类型
ins = i
x := ins.(int)  // 接口转回int类型的实例i
println(x) //输出30

但注意,这时候的i和x在底层不是同一个对象,它们的地址是不一样的。指针

var i int = 30
var ins interface{}

ins = i
x := ins.(int)
println("i addr: ",&i,"x addr: ",&x)

输出:code

0xc042049f68
0xc042049f60

注意,接口实例转回时,接口实例中存放的是什么类型,才能转换成什么类型。同类型的值类型实例和指针类型实例不能互转,不一样类型更不能互转。对象

在不能转换时,Golang将直接以Panic的方式终止程序。但能够处理转换失败时的panic,这时须要类型断言,也即类型检测。接口

接口类型探测:类型断言

类型探测的方式和类型转换的方式都是ins.(Type)ins.(*Type)。当处于单个返回值上下文时,作的是类型转换,当处于两个返回值的上下文时,作的是类型探测。类型探测的第一个返回值是类型转换以后的类型实例,第二个返回值是布尔型的ok返回值。it

// 若是ins保存的是值类型的Type,则输出
if t, ok := ins.(Type); ok {
    fmt.Printf("%T\n", v)
}

// 若是ins保存的是指针类型的*Type,则输出
if t, ok := ins.(*Type); ok {
    fmt.Printf("%T\n", v)
}

// 一个返回值的探测
t := ins.(Type)
t := ins.(*Type)

如下是一个例子:io

package main

import "fmt"

// Shaper 接口类型
type Shaper interface {
    Area() float64
}

// Square struct类型
type Square struct {
    length float64
}

// Square类型实现Shaper中的方法Area()
func (s Square) Area() float64 {
    return s.length * s.length
}

func main() {
    var ins1, ins2 Shaper

    // 指针类型的实例
    s1 := new(Square)
    s1.length = 3.0
    ins1 = s1
    if v, ok := ins1.(*Square); ok {
        fmt.Printf("ins1: %T\n", v)
    }

    // 值类型的实例
    s2 := Square{4.0}
    ins2 = s2
    if v, ok := ins2.(Square); ok {
        fmt.Printf("ins2: %T\n", v)
    }
}

上面两个Printf都会输出,由于它们的类型判断都返回true。若是将ins2.(Square)改成ins2.(*Square),第二个Printf将不会输出,由于ins2它保存的是值类型的实例。import

如下是输出结果:变量

ins1: *main.Square
ins2: main.Square

特别须要注意的是,ins必须明确是接口实例。例如,如下前两种声明是有效的,第三种推断类型是错误的,由于它多是接口实例,也多是类型的实例副本。语法

var ins Shaper     // 正确
ins := Shaper(s1)  // 正确
ins := s1          // 错误

当ins不能肯定是接口实例时,用它来进行测试,例如ins.(Square)将会报错:

invalid type assertion:ins.(Square) (non-interface type (type of ins) on left)

它说明了左边的ins是非接口类型(non-interface type)。

另外一方面,经过接口类型断言(ins.(Type)),若是Type是一个接口类型,就能够判断接口实例ins中所保存的类型是否也实现了Type接口。例如:

var r io.Read
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
    return nil, err
}
r = tty

var w io.Writer
w = r.(io.Writer)

上面的r是io.Read接口的一个实例变量,它里面保存的是tty和它的类型,即(tty, *os.File),而后断言r的类型,探测它里面的类型*File是否也实现了io.Writer接口,若是实现了,则保存到io.Writer接口的实例变量w中,这样w实例也将保存(tty,*os.File)

因为任意内容都实现了空接口,因此,老是能够把一个接口实例无需经过任何断言地赋值给一个空接口实例:

var empty interface{}
empty = w

如今empty也保存了(tty,*os.File)

type Switch结构

直接用if v,ok := ins.(Type);ok {}的方式作类型探测在探测类型数量多时不是很方便,须要重复写if结构。

Golang提供了switch...case结构用于作多种类型的探测,因此这种结构也称为type-switch。这是比较方便的语法,好比能够判断某接口若是是A类型,就执行A类型里的特有方法,若是是B类型,就执行B类型里的特有方法。

用法以下:

switch v := ins.(type) {
case *Square:
    fmt.Printf("Type Square %T\n", v)
case *Circle:
    fmt.Printf("Type Circle %T\n", v)
case nil:
    fmt.Println("nil value: nothing to check?")
default:
    fmt.Printf("Unexpected type %T", v)
}

其中ins.(type)中的小写type是固定的词语。

如下是一个使用示例:

package main

import (
    "fmt"
)

// Shaper 接口类型
type Shaper interface {
    Area() float64
}

// Circle struct类型
type Circle struct {
    radius float64
}

// Circle类型实现Shaper中的方法Area()
func (c *Circle) Area() float64 {
    return 3.14 * c.radius * c.radius
}

// Square struct类型
type Square struct {
    length float64
}

// Square类型实现Shaper中的方法Area()
func (s Square) Area() float64 {
    return s.length * s.length
}

func main() {
    s1 := &Square{3.3}
    whichType(s1)

    s2 := Square{3.4}
    whichType(s2)

    c1 := new(Circle)
    c1.radius = 2.3
    whichType(c1)
}

func whichType(n Shaper) {
    switch v := n.(type) {
    case *Square:
        fmt.Printf("Type Square %T\n", v)
    case Square:
        fmt.Printf("Type Square %T\n", v)
    case *Circle:
        fmt.Printf("Type Circle %T\n", v)
    case nil:
        fmt.Println("nil value: nothing to check?")
    default:
        fmt.Printf("Unexpected type %T", v)
    }
}

上面的type-switch中,之因此没有加上case Circle,是由于Circle只实现了指针类型的receiver,根据Method Set对接口的实现规则,只有指针类型的Circle示例才算是实现了接口Shaper,因此将值类型的示例case Circle放进type-switch是错误的。

相关文章
相关标签/搜索