类型断言解析

经常地我们对一个接口值的动态类型是不确定的,如方法的形参为接口类型时,此时就需要检验它是否符合我们需要的类型。 类型断言是一个使用在 接口值上 的操作。

断言类型的语法:x.(T)

这里x表示一个接口的类型,T表示一个类型(也可为接口类型)。 一个类型断言检查一个接口对象x的动态类型是否和断言的类型T匹配。

类型断言分两种情况: 第一种,如果断言的类型T是一个具体类型,类型断言x.(T)就检查x的动态类型是否和T的类型相同。

  • 如果这个检查成功了,类型断言的结果是一个类型为T的对象,该对象的值为接口变量x的动态值。换句话说,具体类型的类型断言从它的操作对象中获得具体的值。
  • 如果检查失败,接下来这个操作会抛出panic,除非用两个变量来接收检查结果,如:f, ok := w.(*os.File)

第二种,如果断言的类型T是一个接口类型,类型断言x.(T)检查x的动态类型是否满足T接口。

  • 如果这个检查成功,则检查结果的接口值的动态类型和动态值不变,但是该接口值的类型被转换为接口类型T。换句话说,对一个接口类型的类型断言改变了类型的表述方式,改变了可以获取的方法集合(通常更大),但是它保护了接口值内部的动态类型和值的部分。
  • 如果检查失败,接下来这个操作会抛出panic,除非用两个变量来接收检查结果,如:f, ok := w.(io.ReadWriter)

注意:

如果断言的操作对象x是一个nil接口值,那么不论被断言的类型T是什么这个类型断言都会失败。 我们几乎不需要对一个更少限制性的接口类型(更少的方法集合)做断言,因为它表现的就像赋值操作一样,除了对于nil接口值的情况。

//interface
type Tester interface{
    getName() string
}

type Teater2 interface{
    printName()
}

//person类型
type Person struct{
    name string
}

func (p Person)getName() string {
    return p.name
}

func (p Person) printName() {
    fmt.Println(p.name)
}

func main(){
    var t Tester
    t=Person{"tmac"}
    check(t)
}

func check(t Tester) {
    //第一种情况
    if f,ok1:=t.(Person); ok1{
        fmt.Printf("%T\n%s\n",f,f.getName())
    }
    //第二种情况
    if t, ok2 := t.(Tester2);ok2 {  //重用变量名t(无需重新声明)
        check2(t) //若类型断言为true,则新的t被转型为Tester2接口类型,但其动态类型和动态值不变
    }
}

func check2(t Tester2)  {
    t.printName()
}

输出结果:

main.Person
tmac
tmac

断言的使用

golang的语言中提供了断言的功能。golang中的所有程序都实现了interface{}的接口,这意味着,所有的类型如string,int,int64甚至是自定义的struct类型都就此拥有了interface{}的接口,这种做法和java中的Object类型比较类似。那么在一个数据通过func funcName(interface{})的方式传进来的时候,也就意味着这个参数被自动的转为interface{}的类型。

如以下的代码:

func funcName(a interface{}) string{
    return(a)
}

编译器将返回: cannot convert a (type interface{}) to type string: need type assertion

此时,意味着整个转化的过程需要类型断言。类型断言有以下几种形式:

  1. 直接断言使用

var a interface{}

fmt.Println(“Where are you,Jonny?”, a.(string))

但是如果断言失败一般会导致panic的发生。所以为了防止panic的发生,我们需要在断言前进行一定的判断

value, ok := a.(string)

如果断言失败,那么ok的值将会是false,但是如果断言成功ok的值将会是true,同时value将会得到所期待的正确的值。示例:

value,ok:=a.(string)
if !ok{
    fmt.Println("It's not ok for type string")
    return
}

fmt.Println("The value is ", value)

另外也可以配合switch语句进行判断:

var t interface{}

t = functionOfSomeType()
switch t := t.(type) {
default:
    fmt.Printf("unexpected type %T", t)       // %T prints whatever type t has
case bool:
    fmt.Printf("boolean %t\n", t)             // t has type bool
case int:
    fmt.Printf("integer %d\n", t)             // t has type int
case *bool:
    fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
    fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}

另外补充几个go语言编程的小tips 1)如果不符合要求可以尽快的return(return as fast as you can),而减少else语句的使用,这样可以更加直观一些。

2)转换类型的时候如果是string可以不用断言,使用fmt.Sprint()函数可以达到想要的效果

3)变量的定义和申明可以用组的方式,如:

var (
   a string
   b int
   c int64
   ...
)


import (
    "fmt"
    "strings"
    "net/http"
   ...
)

4) 函数逻辑比较复杂,可以把一些逻辑封装成一个函数,提高可读性。 5)使用net/http包和net/url包的函数,可能会带有url encode功能,需要注意