如何检查 interface{} 是否为切片类型

16

我不太懂Go :) 所以我的问题可能很愚蠢,但找不到答案。

我需要一个函数:

func name (v interface{}) {
    if is_slice() {
        for _, i := range v {
            my_var := i.(MyInterface)
            ... do smth
        }
    } else {
        my_var := v.(MyInterface)
        ... do smth
    }
}

我该如何在 Go 中使用is_slice?感激不尽任何帮助。


实际上我的问题是v是指针的切片。经过一些调查,我认为最好首先迭代集合,至少我见过的实现都使用了这种方法。有一些复制/粘贴样板代码,但我认为现在不会有问题。 - Alexander
1
可能是 range over interface{} which stores a slice 的重复问题。 - Tarion
4个回答

17

is_slice方法可以是这样的:

func IsSlice(v interface{}) bool {
    return reflect.TypeOf(v).Kind() == reflect.Slice
}

如果需要的话,还可以加入额外的条件reflect.TypeOf(v).Kind() == reflect.Array


2
这是实际的问题答案。谢谢! - Shlomi
使用反射将 interface{} 转换为 interface{} 数组(或切片)是否可行? - Alex SHP
// InterfaceSlice函数将任意对象的切片转换为这些内部对象的接口切片 // 如果in不是可寻址的项,它将引发panic异常 func InterfaceSlice(in any) (o []any) { if in == nil { return } v := reflect.ValueOf(in) for i := 0; i < v.Len(); i++ { o = append(o, v.Index(i).Interface()) } return o } - spekary

15

在您的情况下,类型切换是最简单和最方便的解决方案:

func name(v interface{}) {
    switch x := v.(type) {
    case []MyInterface:
        fmt.Println("[]MyInterface, len:", len(x))
        for _, i := range x {
            fmt.Println(i)
        }
    case MyInterface:
        fmt.Println("MyInterface:", x)
    default:
        fmt.Printf("Unsupported type: %T\n", x)
    }
}
case分支列举了可能的类型,在它们内部,x变量已经是该类型,因此您可以直接使用它。

测试:

type MyInterface interface {
    io.Writer
}

var i MyInterface = os.Stdout
name(i)
var s = []MyInterface{i, i}
name(s)
name("something else")

输出结果(在Go Playground上尝试):

MyInterface: &{0x1040e110}
[]MyInterface, len: 2
&{0x1040e110}
&{0x1040e110}
Unsupported type: string

对于单个类型检查,您也可以使用类型断言

if x, ok := v.([]MyInterface); ok {
    // x is of type []MyInterface
    for _, i := range x {
        fmt.Println(i)
    }
} else {
    // x is not of type []MyInterface or it is nil
}

还有其他的方法,使用包reflect,你可以编写一个更通用(但速度较慢)的解决方案,但如果你刚开始学习Go,不应该深入研究反射。


3

icza的回答是正确的,但并不被go的创建者们推荐:

interface{}什么都没说

一个更好的方法可能是为您拥有的每种类型定义一个函数:

func name(v MyInterface) {
    // do something
}

func names(vs []MyInterface) {
    for _, v := range(vs) {
        name(v)
    }
}

3
为什么 fmt 包的创造者没有为每种类型创建一个打印函数呢? - Alexander
感谢您提供有趣的链接。 - Alexander
1
@Alexander,你已经实现了一种“每种类型都打印”的方法,使用Print动词:%s用于字符串,%p用于指针,%d用于十进制整数等。你可以使用%s并实现自己的String()字符串函数来打印你自己的类型。 - Lucas Gabriel Sánchez
请注意,在这种情况下,您将无法直接将值切片传递到函数中,因为接口切片不是一个接口:https://github.com/golang/go/wiki/InterfaceSlice - Daniel Titkov

0

来自https://blog.golang.org/json

解码任意数据

考虑以下 JSON 数据,存储在变量 b 中:

b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)

如果不知道数据的结构,我们可以通过Unmarshal将其解码为interface{}值:

var f interface{} err := json.Unmarshal(b, &f) 此时f中的Go值将是一个映射,其键为字符串,其值本身存储为空接口值:

f = map[string]interface{}{
    "Name": "Wednesday",
    "Age":  6,
    "Parents": []interface{}{
        "Gomez",
        "Morticia",
    },
}

要访问这些数据,我们可以使用类型断言来访问 f 的底层 map[string]interface{}:
m := f.(map[string]interface{})

我们可以使用range语句遍历该映射,并使用类型开关访问其值作为它们的具体类型:
for k, v := range m {
    switch vv := v.(type) {
    case string:
        fmt.Println(k, "is string", vv)
    case float64:
        fmt.Println(k, "is float64", vv)
    case []interface{}:
        fmt.Println(k, "is an array:")
        for i, u := range vv {
            fmt.Println(i, u)
        }
    default:
        fmt.Println(k, "is of a type I don't know how to handle")
    }
}

以这种方式,您可以在仍然享受类型安全的好处的同时处理未知的JSON数据。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接