40.Go 反射

Go是一种静态类型的语言。 在大多数情况下,变量的类型在编译时是已知的。接口类型是一种例外,尤其是空接口interface {}。

空接口是一种动态类型,类似于Java或C#中的Object。

在编译时,我们无法确定接口类型的基础值是整数还是字符串。

标准库中的reflect包,使我们可以在运行时使用此类动态值。 我们可以:

  • 检查动态值的类型
  • 枚举结构的字段
  • 设定值
  • 在运行时创建新值
    用于在运行时检查接口值的类型的相关语言级功能是类型switch和类型断言:
var v interface{} = 4
var reflectVal reflect.Value = reflect.ValueOf(v)

var typ reflect.Type = reflectVal.Type()
fmt.Printf("Type '%s' of size: %d bytes\n", typ.Name(), typ.Size())
if typ.Kind() == reflect.Int {
    fmt.Printf("v contains value of type int\n")
}

Type 'int' of size: 8 bytes
v contains value of type int

反射的基础是:

  • 以空接口interface{}类型的值开头
  • 使用reflect.ValueOf(v interface {})来获取reflect.Value,它代表有关该值的信息
  • 使用reflect.Value检查值的类型,测试值是否为nil,设置值

反射有几种实际用途。

原始类型

让我们看看可以对基本类型(例如int或string)执行哪种操作。

获取类型

func printType(v interface{}) {
    rv := reflect.ValueOf(v)
    typ := rv.Type()
    typeName := ""
    switch rv.Kind() {
    case reflect.Ptr:
        typeName = "pointer"
    case reflect.Int:
        typeName = "int"
    case reflect.Int32:
        typeName = "int32"
    case reflect.String:
        typeName = "string"
    // ... handle more cases
    default:
        typeName = "unrecognized type"
    }
    fmt.Printf("v is of type '%s'. Size: %d bytes\n", typeName, typ.Size())
}

printType(int32(3))
printType("")
i := 3
printType(&i) // *int i.e. pointer to int

v is of type 'int32'. Size: 4 bytes
v is of type 'string'. Size: 16 bytes
v is of type 'pointer'. Size: 8 bytes

在真实代码中,你需要处理关心的所有类型。

获取值

func getIntValue(v interface{}) {
    var reflectValue = reflect.ValueOf(v)
    n := reflectValue.Int()
    fmt.Printf("Int value is: %d\n", n)
}

getIntValue(3)
getIntValue(int8(4))
getIntValue("")

Int value is: 3
Int value is: 4
panic: reflect: call of reflect.Value.Int on string Value

goroutine 1 [running]:
reflect.Value.Int(...)
/usr/local/go/src/reflect/value.go:986
main.getIntValue(0x4a0120, 0x4db0d0)
/tmp/src282322283/main.go:14 +0x204
main.main()
/tmp/src282322283/main.go:24 +0x7b
exit status 2

为了最大程度地减少API表面,Int()返回int64并对所有带符号的整数值(int8,int16,int32,int64)有效。

UInt()方法返回uint64并对每个无符号整数值(uint8,uint16,uint32,uint64)有效。

试图从不兼容类型(如字符串)的值中获取整数值会引发panic。

为了避免出现panic,您可以先使用Kind()检查类型。

检索值的所有方法:

  • Bool() bool
  • Int() int64
  • UInt() uint64
  • Float() float64
  • String() string
  • Bytes() []byte

设置值

type S struct {
    N int
}

func setIntPtr() {
    var n int = 2
    reflect.ValueOf(&n).Elem().SetInt(4)
    fmt.Printf("setIntPtr: n=%d\n", n)
}

func setStructFieldDirect() {
    var s S
    reflect.ValueOf(&s.N).Elem().SetInt(5)
    fmt.Printf("setStructFieldDirect: n=%d\n", s.N)
}

func setStructPtrField() {
    var s S
    reflect.ValueOf(&s).Elem().Field(0).SetInt(6)
    fmt.Printf("setStructPtrField: s.N: %d\n", s.N)
}

func handlePanic(funcName string) {
    if msg := recover(); msg != nil {
        fmt.Printf("%s panicked with '%s'\n", funcName, msg)
    }
}

func setStructField() {
    defer handlePanic("setStructField")
    var s S
    reflect.ValueOf(s).Elem().Field(0).SetInt(4)
    fmt.Printf("s.N: %d\n", s.N)
}

func setInt() {
    defer handlePanic("setInt")
    var n int = 2
    rv := reflect.ValueOf(n)
    rv.Elem().SetInt(4)
}

func setIntPtrWithString() {
    defer handlePanic("setIntPtrWithString")
    var n int = 2
    reflect.ValueOf(&n).Elem().SetString("8")
}

setIntPtr: n=4
setStructFieldDirect: n=5
setStructPtrField: s.N: 6
setInt panicked with 'reflect: call of reflect.Value.Elem on int Value'
setStructField panicked with 'reflect: call of reflect.Value.Elem on struct Value'
setIntPtrWithString panicked with 'reflect: call of reflect.Value.SetString on int Value'

如setInt和setStructField所示,只有从指向值的指针开始,才可以更改值。

如setInt和setStructField所示,只有从指向值的指针开始,才可以更改值。

setStructPtrField显示如何通过字段值在结构中的位置来获取对字段值的引用。

尝试设置不兼容类型的值会引发panic。

设置值的方法反映了读取值的方法:

  • SetBool(v bool)
  • SetInt(v int64)
  • SetUInt(v uint64)
  • SetFloat(v float64)
  • SetString(v string)
  • SetBytes(v []byte)

指针

指向X的指针与X是不同的类型。

如果reflect.Value引用了指向值的指针,则可以通过调用Elem()获得对该值的引用。

func printIntResolvingPointers(v interface{}) {
    rv := reflect.ValueOf(v)
    typeName := rv.Type().String()
    name := ""
    for rv.Kind() == reflect.Ptr {
        name = "pointer to " + name
        rv = rv.Elem()
    }
    name += rv.Type().String()
    fmt.Printf("Value: %d. Type: '%s' i.e. '%s'.\n\n", rv.Int(), name, typeName)
}

func main() {
    n := 3
    printIntResolvingPointers(n)

    n = 4
    printIntResolvingPointers(&n)

    n = 5
    np := &n
    printIntResolvingPointers(&np)
}

Value: 3. Type: 'int' i.e. 'int'.

Value: 4. Type: 'pointer to int' i.e. '*int'.

Value: 5. Type: 'pointer to pointer to int' i.e. '**int'.

在底层,接口也是指向其基础值的指针,因此Elem()也可以在代表接口的reflect.Value上工作。

struct

列出struct的字段

使用反射,我们可以列出struct的所有字段。

type S struct {
    FirstName string `my_tag:"first-name"`
    lastName  string
    Age       int `json:"age",xml:"AgeXml`
}

func describeStructSimple(rv reflect.Value) {
    structType := rv.Type()
    for i := 0; i < rv.NumField(); i++ {
        v := rv.Field(i)
        structField := structType.Field(i)
        name := structField.Name
        typ := structField.Type
        tag := structField.Tag
        jsonTag := tag.Get("json")
        isExported := structField.PkgPath == ""
        if isExported {
            fmt.Printf("name: '%s',\ttype: '%s', value: %v,\ttag: '%s',\tjson tag: '%s'\n", name, typ, v.Interface(), tag, jsonTag)
        } else {
            fmt.Printf("name: '%s',\ttype: '%s',\tvalue: not accessible\n", name, v.Type().Name())
        }
    }
}

func main() {
    s := S{
        FirstName: "John",
        lastName:  "Doe",
        Age:       27,
    }
    describeStructSimple(reflect.ValueOf(s))
}

name: 'FirstName', type: 'string', value: John, tag: 'my_tag:"first-name"', json tag: ''
name: 'lastName', type: 'string', value: not accessible
name: 'Age', type: 'int', value: 27, tag: 'json:"age",xml:"AgeXml', json tag: 'age'

使用反射,我们只能访问导出的字段的值(v.Interface {})。

导出的字段是名称以大写字母开头的字段(导出的是“姓氏”和“年龄”,而不是“ lastName”)。

如果reflect.StructField.PkgPath ==“”,则导出字段。

递归列出结构的字段

检查struct本质上是递归过程。

您必须追逐指针并递归到嵌入式struct中。

在实际程序中,使用反射检查struct是递归的。

type Inner struct {
    N int
}

type S struct {
    Inner
    NamedInner Inner
    PtrInner   *Inner
    unexported int
    N          int8
}

func indentStr(level int) string {
    return strings.Repeat("  ", level)
}

// if sf is not nil, this is a field of a struct
func describeStruct(level int, rv reflect.Value, sf *reflect.StructField) {
    structType := rv.Type()
    nFields := rv.NumField()
    typ := rv.Type()
    if sf == nil {
        fmt.Printf("%sstruct %s, %d field(s), size: %d bytes\n", indentStr(level), structType.Name(), nFields, typ.Size())
    } else {
        fmt.Printf("%sname: '%s' type: 'struct %s', offset: %d, %d field(s), size: %d bytes, embedded: %v\n", indentStr(level), sf.Name, structType.Name(), sf.Offset, nFields, typ.Size(), sf.Anonymous)
    }

    for i := 0; i < nFields; i++ {
        fv := rv.Field(i)
        sf := structType.Field(i)
        describeType(level+1, fv, &sf)
    }
}

// if sf is not nil, this is a field of a struct
func describeType(level int, rv reflect.Value, sf *reflect.StructField) {
    switch rv.Kind() {

    case reflect.Int, reflect.Int8:
        // in real code we would handle more primitive types
        i := rv.Int()
        typ := rv.Type()
        if sf == nil {
            fmt.Printf("%stype: '%s', value: '%d'\n", indentStr(level), typ.Name(), i)
        } else {
            fmt.Printf("%s name: '%s' type: '%s', value: '%d', offset: %d, size: %d\n", indentStr(level), sf.Name, typ.Name(), i, sf.Offset, typ.Size())
        }

    case reflect.Ptr:
        fmt.Printf("%spointer\n", indentStr(level))
        describeType(level+1, rv.Elem(), nil)

    case reflect.Struct:
        describeStruct(level, rv, sf)
    }
}

func main() {
    var s S
    describeType(0, reflect.ValueOf(s), nil)
}

struct S, 5 field(s), size: 40 bytes
name: 'Inner' type: 'struct Inner', offset: 0, 1 field(s), size: 8 bytes, embedded: true
name: 'N' type: 'int', value: '0', offset: 0, size: 8
name: 'NamedInner' type: 'struct Inner', offset: 8, 1 field(s), size: 8 bytes, embedded: false
name: 'N' type: 'int', value: '0', offset: 0, size: 8
pointer
name: 'unexported' type: 'int', value: '0', offset: 24, size: 8
name: 'N' type: 'int8', value: '0', offset: 32, size: 1

slice

使用反射读取切片

a := []int{3, 1, 8}
rv := reflect.ValueOf(a)

fmt.Printf("len(a): %d\n", rv.Len())
fmt.Printf("cap(a): %d\n", rv.Cap())

fmt.Printf("slice kind: '%s'\n", rv.Kind().String())

fmt.Printf("element type: '%s'\n", rv.Type().Elem().Name())

el := rv.Index(0).Interface()
fmt.Printf("a[0]: %v\n", el)

elRef := rv.Index(1)
fmt.Printf("elRef.CanAddr(): %v\n", elRef.CanAddr())
fmt.Printf("elRef.CanSet(): %v\n", elRef.CanSet())

elRef.SetInt(5)
fmt.Printf("a: %v\n", a)

len(a): 3
cap(a): 3
slice kind: 'slice'
element type: 'int'
a[0]: 3
elRef.CanAddr(): true
elRef.CanSet(): true
a: [3 5 8]

使用反射创建新切片

typ := reflect.SliceOf(reflect.TypeOf("example"))
// create slice with capacity 10 and length 1
rv := reflect.MakeSlice(typ, 1, 10)
rv.Index(0).SetString("foo")

a := rv.Interface().([]string)
fmt.Printf("a: %#v\n", a)

a: []string{"foo"}

reflect.Kind

Reflection.Value上的函数Kind()返回描述值类型的reflect.Kind类型。

这是所有可能的值:

type Kind uint

const (
    Invalid Kind = iota
    Bool
    Int
    Int8
    Int16
    Int32
    Int64
    Uint
    Uint8
    Uint16
    Uint32
    Uint64
    Uintptr
    Float32
    Float64
    Complex64
    Complex128
    Array
    Chan
    Func
    Interface
    Map
    Ptr
    Slice
    String
    Struct
    UnsafePointer
)

反射用途

序列化为JSON,XML,SQL,protobufs

反射使实现通用JSON序列化/反序列化成为可能。

对于通用JSON序列化,我们可以枚举任意结构的字段,读取它们的字段并创建相应的JSON字符串。

对于一般的JSON反序列化,我们可以枚举任意结构的字段并根据已解析的JSON数据进行设置。

其他序列化格式(例如XML,YAML或协议缓冲区)也是如此。

反射使为SQL数据库定义通用API成为可能,因为我们可以将任意结构转换为SQL数据库可以理解的格式,并将从SQL数据库接收的数据放入任意结构。

使用Go函数扩展模板语言

由于能够在运行时调用任意函数,因此我们可以在文本/模板中为模板定义自定义函数。 我们向模板引擎注册Go函数。

然后,引擎可以在执行模板时在运行时调用这些函数。

编写与Go紧密集成的口译员

由于反射功能可以在运行时调用任意函数,因此JavaScript解释器可以轻松地扩展为用Go编写的其他函数。

你可能感兴趣的:(40.Go 反射)