万物皆可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,
    }
}
  • 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

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

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
}