我很好奇为什么 Go 在将 T
隐式转换为 interface{}
时不会将 []T
隐式转换为 []interface{}
。我错过了这种转换是否有一些重要的东西?
例子:
func foo([]interface{}) { /* do something */ }
func main() {
var a []string = []string{"hello", "world"}
foo(a)
}
go build
投诉
不能在函数参数中使用 (type []string) 作为类型 []interface {}
如果我尝试明确地这样做,同样的事情:b := []interface{}(a)
抱怨
无法将 (type []string) 转换为 type []interface {}
所以每次我需要进行这种转换(这似乎经常出现)时,我一直在做这样的事情:
b = make([]interface{}, len(a), len(a))
for i := range a {
b[i] = a[i]
}
有没有更好的方法来做到这一点,或者标准库函数来帮助这些转换?每次我想调用一个可以获取整数或字符串列表的函数时,多写 4 行代码似乎有点愚蠢。
在 Go 中,有一条通用规则,即语法不应隐藏复杂/昂贵的操作。
将 string
转换为 interface{}
在 O(1) 时间内完成。将 []string
转换为 interface{}
也在 O(1) 时间内完成,因为切片仍然是一个值。但是,将 []string
转换为 []interface{}
需要 O(n) 时间,因为切片的每个元素都必须转换为 interface{}
。
此规则的一个例外是转换字符串。在将 string
与 []byte
或 []rune
相互转换时,即使转换是“语法”,Go 也能工作 O(n)。
没有标准库函数可以为您进行这种转换。不过,您最好的选择就是使用您在问题中提供的代码行:
b := make([]interface{}, len(a))
for i := range a {
b[i] = a[i]
}
否则,您可以使用 reflect
创建一个,但它会比三行选项慢。反射示例:
func InterfaceSlice(slice interface{}) []interface{} {
s := reflect.ValueOf(slice)
if s.Kind() != reflect.Slice {
panic("InterfaceSlice() given a non-slice type")
}
// Keep the distinction between nil and empty slice input
if s.IsNil() {
return nil
}
ret := make([]interface{}, s.Len())
for i:=0; i<s.Len(); i++ {
ret[i] = s.Index(i).Interface()
}
return ret
}
您缺少的是具有 T
值的 T
和 interface{}
在内存中具有不同的表示形式,因此无法进行简单的转换。
T
类型的变量只是它在内存中的值。没有关联的类型信息(在 Go 中,每个变量都有一个在编译时而不是在运行时已知的单一类型)。它在内存中表示如下:
价值
保存类型为 T
的变量的 interface{}
在内存中表示如下
指向类型 T 的指针
价值
所以回到你原来的问题:为什么 go 不会将 []T
隐式转换为 []interface{}
?
将 []T
转换为 []interface{}
将涉及创建一个新的 interface {}
值切片,这是一个不平凡的操作,因为内存中的布局完全不同。
以下是官方解释:https://github.com/golang/go/wiki/InterfaceSlice
var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
interfaceSlice[i] = d
}
[]interface
转换为我期望的类型,例如 []map[string]interface{}
?
请改用 interface{}
。要作为切片回滚,请尝试
func foo(bar interface{}) {
s := bar.([]string)
// ...
}
bar
以便将其解释为“任何类型的切片”?请注意,他的三行创建一个 []interface{}
,而不是 []string
或其他具体类型的切片。
func foo(bar []string) { /* ... */ }
interface{}
将使编译器在传递 foo(a)
中的参数时不会抱怨。他可以在 foo
中使用反射或类型切换 (golang.org/doc/effective_go.html#type_switch)。
在 Go 1.18 或更高版本中,使用以下函数将任意切片类型转换为 []interface{}
或其别名 any
:
func ToSliceOfAny[T any](s []T) []any {
result := make([]any, len(s))
for i, v := range s {
result[i] = v
}
return result
}
Go 1.18 泛型特性并没有消除将任意切片转换为 []any
的需要。下面是需要转换的示例:应用程序希望 query a database 使用 []string
的元素作为声明为 args ...any
的可变参数查询参数。此答案中的功能允许应用程序以方便的单行方式查询数据库:
rows, err := db.Query(qs, ToSliceOfAny(stringArgs)...)
如果您需要更多缩短代码,您可以为助手创建新类型
type Strings []string
func (ss Strings) ToInterfaceSlice() []interface{} {
iface := make([]interface{}, len(ss))
for i := range ss {
iface[i] = ss[i]
}
return iface
}
然后
a := []strings{"a", "b", "c", "d"}
sliceIFace := Strings(a).ToInterfaceSlice()
我很好奇通过反射转换接口数组与在循环中执行它相比要慢多少,如 Stephen's answer 中所述。这是两种方法的基准比较:
benchmark iter time/iter bytes alloc allocs
--------- ---- --------- ----------- ------
BenchmarkLoopConversion-12 2285820 522.30 ns/op 400 B/op 11 allocs/op
BenchmarkReflectionConversion-12 1780002 669.00 ns/op 584 B/op 13 allocs/op
因此,使用循环比通过反射快约 20%。
这是我的测试代码,以防您想验证我是否正确地做事:
import (
"math/rand"
"reflect"
"testing"
"time"
)
func InterfaceSlice(slice interface{}) []interface{} {
s := reflect.ValueOf(slice)
if s.Kind() != reflect.Slice {
panic("InterfaceSlice() given a non-slice type")
}
// Keep the distinction between nil and empty slice input
if s.IsNil() {
return nil
}
ret := make([]interface{}, s.Len())
for i := 0; i < s.Len(); i++ {
ret[i] = s.Index(i).Interface()
}
return ret
}
type TestStruct struct {
name string
age int
}
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func randSeq(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
func randTestStruct(lenArray int, lenMap int) map[int][]TestStruct {
randomStructMap := make(map[int][]TestStruct, lenMap)
for i := 0; i < lenMap; i++ {
var testStructs = make([]TestStruct, 0)
for k := 0; k < lenArray; k++ {
rand.Seed(time.Now().UnixNano())
randomString := randSeq(10)
randomInt := rand.Intn(100)
testStructs = append(testStructs, TestStruct{name: randomString, age: randomInt})
}
randomStructMap[i] = testStructs
}
return randomStructMap
}
func BenchmarkLoopConversion(b *testing.B) {
var testStructMap = randTestStruct(10, 100)
b.ResetTimer()
for i := 0; i < b.N; i++ {
obj := make([]interface{}, len(testStructMap[i%100]))
for k := range testStructMap[i%100] {
obj[k] = testStructMap[i%100][k]
}
}
}
func BenchmarkReflectionConversion(b *testing.B) {
var testStructMap = randTestStruct(10, 100)
b.ResetTimer()
for i := 0; i < b.N; i++ {
obj := make([]interface{}, len(testStructMap[i%100]))
obj = InterfaceSlice(testStructMap[i%100])
_ = obj
}
}
prettybench
或 benchcmp
-bench
更新了基准。
将 interface{}
转换为任何类型。
句法:
result := interface.(datatype)
例子:
var employee interface{} = []string{"Jhon", "Arya"}
result := employee.([]string) //result type is []string.
invalid type assertion: potato.([]string) (non-interface type []interface {} on left)
不定期副业成功案例分享
map
。channels
。Converting a []string to an interface{} is also done in O(1) time
?