美文网首页
golang的空接口(interface {})类型判断 202

golang的空接口(interface {})类型判断 202

作者: 9_SooHyun | 来源:发表于2021-08-03 21:19 被阅读0次

    万物皆可interface{}

    在go里面,任何数据类型的实例变量都可以认为实现了一个空接口,即interface{}这个类型可以“容纳”任何其他的数据类型,这给go的泛型提供了一种实现

    注意,interface{}是一个类型,而interface只是一个关键字
    我们知道,利用type可以重新给已有类型重命名,就像

    type iface interface{}
    var i iface
    

    而不能够

    type iface interface
    

    interface{}的“泛型”

    例如

    package main
    
    import "fmt"
    
    func main() {    
        var i []interface{}
        i = append(i, 1)
        i = append(i, "23")
        i = append(i, []int{4,5,6})
        fmt.Println(i)
    }
    

    输出

    [1 23 [4 5 6]]
    

    这就实现了类似python中的list的功能,可以接纳不同类型的元素。但是,需要注意的是,当这些 int string 和 slice 元素被append到i之后,它们也无一例外的被转化为了interface{}类型。这是显然的,作为强类型语言,[]interface{}类型的i自然只能接纳interface{}类型的元素

    那么,这里就涉及到一个问题,就是不同类型的变量通过interface{}类型塞到一起,再取出来的时候怎么恢复原来的类型?

    空接口(interface{})的类型判断

    有3种方式

    • type assert 类型断言
      断言就是对接口变量的类型进行检查
      value, ok := element.(T)
      element是interface变量,T是要断言的类型,ok是一个bool类型
      如果断言成功,ok即为true,说明element可以转化为T类型,转化后的值赋值给value
    package main
    
    import "fmt"
    
    func main() {
        container := []interface{}{}
        m1 := make(map[int]string)
        m2 := make(map[string]string)
        m1[1] = "1"
        m2["2"] = "2"
        container = append(container, m1)
        container = append(container, m2)
        fmt.Println(container)
        for _, m := range(container) {
            val, ok := m.(map[int]string)
            if ok {
                fmt.Println("map[int]string", val)
            }
            newval, ok := m.(map[string]string)
            if ok{
                fmt.Println("map[string]string", newval)
            }
        }
    }
    

    执行结果为

    [map[1:1] map[2:2]]
    map[int]string map[1:1]
    map[string]string map[2:2]
    
    • 使用反射机制
      【核心代码】
    retType := reflect.TypeOf(unknow)
    val := reflect.ValueOf(unknow)
    

    例子

    package main
    
    import "fmt"
    import "reflect"
    
    func main() {
        container := []interface{}{}
        m1 := make(map[int]string)
        m2 := make(map[string]string)
        m1[1] = "1"
        m2["2"] = "2"
        container = append(container, m1)
        container = append(container, m2)
        fmt.Println(container)
        for _, m := range(container) {
            retType := reflect.TypeOf(m)
            val := reflect.ValueOf(m)
            fmt.Println(retType, val)
        }
    }
    

    实际上,一个结构体对象作为一个interface{}对象后,要通过反射获取它原来的字段名、字段值和标签,还需要做一些工作,案例如下

    package main
    
    import "fmt"
    import "reflect"
    
    func main() {
        result := f1()
        retType := reflect.TypeOf(result)
        val := reflect.ValueOf(result)
        fmt.Printf("name:'%v' kind:'%v'\n", retType.Name(), retType.Kind()) //name:'' kind:'struct' 
        // 通过reflect.Type.FieldByName找到字段标签
        if namefield, ok := retType.FieldByName("Name"); ok {
            fmt.Println(namefield.Tag) // json:"name"
        }
        
        
        fmt.Println(val, reflect.TypeOf(val).Kind())  // {alice 10} struct
        // 通过reflect.Value.FieldByName找到字段值
        v := val.FieldByName("Name").String()
        fmt.Println(v) // alice
        fmt.Println(reflect.TypeOf(v).Kind()) // string
    }
    
    func f1() interface{} {
        // 返回一个interface{}类型的匿名结构体实例
        return struct{
            Name string `json:"name"`
            Age int
        }{
            Name: "alice",
            Age: 10,
        }
    }
    

    关于反射参考https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-reflect/

    • type关键字判断

    type switch compares types instead of values. You can use this to discover the type of an interface value. In this example, the variable t will have the type corresponding to its clause.

    注意,.(type)必须用于switch case中
    switch unknow.(type){
    case string:
    //string类型todo
    case int:
    //int类型todo
    }

    package main
    
    import "fmt"
    
    func main() {
        container := []interface{}{}
        m1 := make(map[int]string)
        m2 := make(map[string]string)
        m1[1] = "1"
        m2["2"] = "2"
        container = append(container, m1)
        container = append(container, m2)
        fmt.Println(container)
        for _, m := range(container) {
            switch m.(type){
                case map[int]string:
                    // 下面这行的写法是错误的,因为m的type还是interface {}
                    // fmt.Println("map[int]string", m[1])
                    // m进行类型转换
                    v := m.(map[int]string)
                    fmt.Println("map[int]string", v[1])
                case map[string]string:
                    v := m.(map[string]string)
                    fmt.Println("map[int]string", v["2"])
            }
            
        }
    }
    

    结果

    [map[1:1] map[2:2]]
    map[int]string 1
    map[int]string 2
    
    

    注意,type switch这种方法有一个比较隐蔽的坑:

    我们知道,一个switch的case可以支持多个expression,如:

    switch time.Now().Weekday() {
        case time.Saturday, time.Sunday:    // 多个expression
            fmt.Println("It's the weekend")
        default:
            fmt.Println("It's a weekday")
        }
    

    那么在type switch中,如果一个case包含了多个expression,那么实际上在这个case的clause里面得不到具体的类型,而仍然是一个interface{},如:

    package main
    
    import "fmt"
    
    func do(i interface{}) {
        switch v := i.(type) {
            case int, int32, int64:
            fmt.Println(v)
            if v != 0{
                fmt.Println(v, "!=0")
            }
            //fmt.Printf("Twice %v is %v\n", v, v*2)  // 这行代码会报错,因为这个case判断了3个类型,v仍然是空接口interface{},interface{}和2(int)不同类型不能相乘
            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)
        }
    }
    
    func main() {
        var a int
        var b int32
        var c int64
        fmt.Println("test int")
        do(a)
        fmt.Println("test int32")
        do(b)
        fmt.Println("test int64")
        do(c)
        fmt.Println("test string")
        do("hello")
        fmt.Println("test bool")
        do(true)
    }
    
    输出
    test int
    0
    test int32
    0
    0 !=0
    test int64
    0
    0 !=0
    test string
    "hello" is 5 bytes long
    test bool
    I don't know about type bool!
    

    可以看到,在case int, int32, int64这个clause中,v为空接口interface{}类型,而数字0为int类型
    在空接口参与的比较中,首先会比较值的类型,然后再比较值
    所以当v这个空接口保存int32或者int64的时候,就会出现所谓的0 !=0的情况

    关于空接口的比较样例如下:

    package main
    
    import "fmt"
    
    func main() {
        var aa interface{}
        aa = int64(0)
        var b int
        fmt.Println(aa==b) // false
        fmt.Println(aa==0) // false
        fmt.Println(aa==int64(0)) // true
    }
    

    相关文章

      网友评论

          本文标题:golang的空接口(interface {})类型判断 202

          本文链接:https://www.haomeiwen.com/subject/zsaevltx.html