理解Golang中的[]interface{}和interface{}

理解Golang中的[]interface{}interface{}

原文链接: 理解Golang中的[]interface{}interface{}

之前在开发Go项目操作Redis时,利用Do函数进行数据操作,在返回的interface{}类型的转换中踩了一个大坑。

Do(ctx, "HKEYS", "KEY")

在阅读源码中发现,Do方法的是一个[]interface{}切片

func (c *Redis) Do(ctx context.Context, commandName string, args ...interface{}) (interface{}, error) {
	//
    // ...
    //
	reply, err := conn.Do(commandName, args...)
	//
    // ...
    //
	return reply, c.err
}
func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
	return c.DoWithTimeout(c.readTimeout, cmd, args...)
}
func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) {
	//
    // ...
    //
	reply := make([]interface{}, pending)
	//
    // ...
    //
    return reply, err
}

在Goland中有一种特殊类型:interface{}空接口interface{} 类型是没有方法的接口。由于没有 implements 关键字,所以所有类型都至少实现了 0 个方法,所以 所有类型都实现了空接口。这意味着,如果编写一个函数以 interface{} 值作为参数,那么可以为该函数提供任何值,并且,[]interface{}golang中也可以认为是interface{}

func main() {
	method("string")
	method(123)
	method(make(map[string]int))
	method(make([]interface{}, 0))
	method(true)
}

func method(arg interface{}) {

}

所以Do方法的返回值是interface{}类型,但本质上应该是[]interface{}类型,又因为redis的hkeys操作返回的是field字符串数组

在这里插入图片描述

那么上述命令的返回值实际上应该是[]string或者[]byte类型,于是利用golang的类型判定,写出了如下代码

func main() {
	var a interface{} = method()
	bytes := a.([]byte)
	fmt.Println(bytes)
}

func method() interface{} {
	ans := make([]interface{}, 0)
	return append(ans, []byte{96, 97, 98, 99})
}

然而,编译器狠狠的打了我的脸

在这里插入图片描述

既然interface{}能代表任意类型,那么interface{}的切片为什么不能代表任意类型的切片呢?

了解了相关底层数据存储原理后,这个问题也就迎刃而解了

一个interface{}类型的变量的底层存储结构由两个字word组成;一个字用于指向该值底层类型的方法表,另一个字用于指向实际数据

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

所以即使两个变量都是interface{}类型,但底层的类型不同,则两个变量不相等

var (
	a interface{} = 123
	b interface{} = "string"
)
fmt.Println(a == b) // false

那么对于[]interface{}类型的变量来说,切片里的每个元素可以存储不同类型的变量,例如

func main() {
	var a = make([]interface{}, 0)
	a = append(a, []int{123, 456})
	a = append(a, []string{"abc", "ijk"})
	fmt.Println(a) // [[123 456] [abc ijk]]
}

但即使切片里存的数据都是某个特定的类型,也不能通过类型断定来强制转换,因为底层的数据存储结构不同

func main() {
	a := method()
	_, ok := a.([]int)
	fmt.Println(ok) // false
}

func method() interface{} {
	var a = make([]interface{}, 0)
	a = append(a, []int{123, 456})
	a = append(a, []int{789, 111})
	return a
}

Each interface{} takes up two words (one word for the type of what is contained, the other word for either the contained data or a pointer to it). As a consequence, a slice with length N and with type []interface{} is backed by a chunk of data that is N*2 words long.

This is different than the chunk of data backing a slice with type []MyType and the same length. Its chunk of data will be N*sizeof(MyType) words long.

The result is that you cannot quickly assign something of type []MyType to something of type []interface{}; the data behind them just look different.

那么如果我们要把同类型组成的切片转换成的特定类型,可以这样做

func main() {
	a := method()
	ans := make([][]int, 0)
	b, ok := a.([]interface{})
	if ok {
		for _, element := range b {
			ans = append(ans, element.([]int))
		}
	}
	fmt.Println(ans) // [[123 456] [789 111]]
}

func method() interface{} {
	var a = make([]interface{}, 0)
	a = append(a, []int{123, 456})
	a = append(a, []int{789, 111})
	return a
}

参考文章:

InterfaceSlice · golang/go Wiki (github.com)

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值