golang的空接口(interface {})类型判断 202
万物皆可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
}