反射入门


反射(Reflection)是探知数据自身结构的一种能力,不同的语言提供不同的反射机制,在 Go 语言中,反射的能力主要由reflect包提供。

数据的 Type

在先前的文件中,有时会用到reflect.TypeOf()来显示数据的类型名称,实际上,reflect.TypeOf()返回Type的实例,Type是个接口定义,目前包含了以下的方法定义:

type Type interface {
    Align() int
    FieldAlign() int
    Method(int) Method
    MethodByName(string) (Method, bool)
    NumMethod() int
    Name() string
    PkgPath() string
    Size() uintptr
    String() string
    Kind() Kind
    Implements(u Type) bool
    AssignableTo(u Type) bool
    ConvertibleTo(u Type) bool
    Comparable() bool
    Bits() int
    ChanDir() ChanDir
    IsVariadic() bool
    Elem() Type
    Field(i int) StructField
    FieldByIndex(index []int) StructField
    FieldByName(name string) (StructField, bool)
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    In(i int) Type
    Key() Type
    Len() int
    NumField() int
    NumIn() int
    NumOut() int
    Out(i int) Type
}

因此,你可以透过Type的方法定义,获取某个类型的相关结构信息,举例来说:

package main

import (
    "fmt"
    "reflect"
)

type Account struct {
    id      string
    name    string
    balance float64
}

func main() {
    account := Account{"X123", "Justin Lin", 1000}
    t := reflect.TypeOf(account)
    fmt.Println(t.Kind())   // struct
    fmt.Println(t.String()) // main.Account
    /*
       底下显示
       id string
       name string
       balance float64
    */
    for i, n := 0, t.NumField(); i < n; i++ {
        f := t.Field(i)
        fmt.Println(f.Name, f.Type)
    }
}

如果reflect.TypeOf()接受的是个指针,因为指针实际上只是个地址值,必须要透过TypeElem方法获取指针的目标Type,才能获取类型的相关成员:

package main

import (
    "errors"
    "fmt"
    "reflect"
)

type Savings interface {
    Deposit(amount float64) error
    Withdraw(amount float64) error
}

type Account struct {
    id      string
    name    string
    balance float64
}

func (ac *Account) Deposit(amount float64) error {
    if amount <= 0 {
        return errors.New("必须存入正数")
    }
    ac.balance += amount
    return nil
}

func (ac *Account) Withdraw(amount float64) error {
    if amount > ac.balance {
        return errors.New("余额不足")
    }
    ac.balance -= amount
    return nil
}

func main() {
    var savings Savings = &Account{"X123", "Justin Lin", 1000}
    t := reflect.TypeOf(savings)

    for i, n := 0, t.NumMethod(); i < n; i++ {
        f := t.Method(i)
        fmt.Println(f.Name, f.Type)
    }

    if t.Kind() == reflect.Ptr {
        t = t.Elem()
    }

    fmt.Println(t.Kind())
    fmt.Println(t.String())
    for i, n := 0, t.NumField(); i < n; i++ {
        f := t.Field(i)
        fmt.Println(f.Name, f.Type)
    }
}

有上面的范例中,也示范了如何获取接口定义的方法信息,这个范例会显示以下的结果:

Deposit func(*main.Account, float64) error
Withdraw func(*main.Account, float64) error
struct
main.Account
id string
name string

数据的 Kind

上面的范例中,使用了TypeKind()方法,这会返回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
)

以下是个简单的类型测试:

package main

import (
    "fmt"
    "reflect"
)

type Duck struct {
    name string
}

func main() {
    values := [...](interface{}){
        Duck{"Justin"},
        Duck{"Monica"},
        [...]int{1, 2, 3, 4, 5},
        map[string]int{"caterpillar": 123456, "monica": 54321},
        10,
    }

    for _, value := range values {
        switch t := reflect.TypeOf(value); t.Kind() {
        case reflect.Struct:
            fmt.Println("it's a struct.")
        case reflect.Array:
            fmt.Println("it's a array.")
        case reflect.Map:
            fmt.Println("it's a map.")
        case reflect.Int:
            fmt.Println("it's a integer.")
        default:
            fmt.Println("非预期之类型")
        }
    }
}

数据的 Value

如果想实际获得数据的值,可以使用reflect.ValueOf()函数,这会返回Value实例,Value是个结构,定义了一些方法可以使用,可用来获取实际的值,例如:

package main

import (
    "fmt"
    "reflect"
)

type Account struct {
    id      string
    name    string
    balance float64
}

func main() {
    x := 10
    vx := reflect.ValueOf(x)
    fmt.Printf("x = %d\n", vx.Int())

    account := Account{"X123", "Justin Lin", 1000}
    vacct := reflect.ValueOf(account)
    fmt.Printf("id = %s\n", vacct.FieldByName("id").String())
    fmt.Printf("name = %s\n", vacct.FieldByName("name").String())
    fmt.Printf("balance = %.2f\n", vacct.FieldByName("balance").Float())
}

如果是个指针,一样也是要透过Elem()方法获取目标值,例如:

package main

import (
    "fmt"
    "reflect"
)

type Account struct {
    id      string
    name    string
    balance float64
}

func main() {
    x := 10
    vx := reflect.ValueOf(&x)
    fmt.Printf("x = %d\n", vx.Elem().Int())

    account := &Account{"X123", "Justin Lin", 1000}
    vacct := reflect.ValueOf(account).Elem()
    fmt.Printf("id = %s\n", vacct.FieldByName("id").String())
    fmt.Printf("name = %s\n", vacct.FieldByName("name").String())
    fmt.Printf("balance = %.2f\n", vacct.FieldByName("balance").Float())
}

可以透过Value对值进行变动,不过,Value必须是可定址的,具体来说,就是reflect.ValueOf()必须接受指针:

package main

import (
    "fmt"
    "reflect"
)

type Account struct {
    id      string
    name    string
    balance float64
}

func main() {
    x := 10
    vx := reflect.ValueOf(&x).Elem()
    fmt.Printf("x = %d\n", vx.Int()) // x = 10

    vx.SetInt(20)
    fmt.Printf("x = %d\n", x) // x = 20
}

上面的例子若改成以下,就会出现错误:

package main

import (
    "fmt"
    "reflect"
)

type Account struct {
    id      string
    name    string
    balance float64
}

func main() {
    x := 10
    vx := reflect.ValueOf(x)
    fmt.Printf("x = %d\n", vx.Int())

    vx.SetInt(20) // panic: reflect: reflect.Value.SetInt using unaddressable value
    fmt.Printf("x = %d\n", x)
}

技术上来说,上面的例子,只是传了x的值复本给reflect.ValueOf(),因此,对其设值并无意义。

若对反射想进一步研究,可以参考〈The Laws of Reflection〉。


展开阅读全文