萬物皆可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)
}
}
}
執(zhí)行結果為
[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
}