golang 反射

转自:http://golanghome.com/post/546

自己在用Go写Web框架时,遇到要从接口中返回对象信息的技术问题。网上关于Go中接口反射的资料较少,所以自己学习了一段时间,特将结果与大家分享。

代码约定

import (
    "fmt"
    "reflect"
)

type boy struct {
    Name string  
    age  int
}

type human interface {
    SayName()
    SayAge()
}

func (this *boy) SayName() {
    fmt.Println(this.Name)
}

func (this *boy) SayAge() {
    fmt.Println(this.age)
}

func main() {
    // 定义接口变量
    var i human
    // 初始化对象,jown持有对象指针。
    jown := &boy{
        Name: "jown",
        age: 15,
    }
    // 因为boy实现了human中的方法,所以它实现了human接口。
    // 这时,i就指向jown对象。
    i = jown 

    // 通过反射获取接口i 的类型和所持有的值。
    t := reflect.TypeOf(i)
    v := reflect.ValueOf(i)

    // ... 后续操作
}

t,v 的打印结果:

tv 实现了String() string 所对应的接口,所以可以在fmt中打印出来。

fmt.Println(t)
fmt.Println(v)

// 打印结果
*main.boy 
<*main.boy Value>

从上面的打印结果可见:

  1. t 表示i接口的类型为指向main包下struct boy的指针类型。它可以用来存储指向boy的指针。
  2. v 表示i接口目前的所存储值为指向main包下struct boy的指针,也可以理解为上面代码中的jown

得到了这些信息,我们就可以进行后续操作:

通过接口i查询对象的名字

reflect.Type类型下有一个方法Name() string,用来返回不包含包名的类型的名字。所以,我们需要知道i所存储的对象的类型。reflect.Elem() Type可以返回类型的成员类型。这样我们就可以用这两个函数返回对象的名字。

    // 获取i所指向的对象的类型
    structType := t.Elem()
    // 获取对象的名字
    structName := structType.Name()
    fmt.Println(structName)

    // 打印结果
    boy

通过接口i查询对象的方法信息

由于在Go中,func也是一种类型,所以对象的方法存在值和类型的说法。

通过t获取对象方法的信息。

利用t的MethodByName() Method方法:

    method, _ := t.MethodByName("SayAge")
    fmt.Println(method)

    // 打印结果
    {SayAge  func(*main.boy) <func(*main.boy) Value> 1}

它返回了一个对象方法的信息集合即Method,关于Method结构定义在reflect/type.go下具体为:

// Method represents a single method.
type Method struct {
    // Name is the method name.
    // PkgPath is the package path that qualifies a lower case (unexported)
    // method name.  It is empty for upper case (exported) method names.
    // The combination of PkgPath and Name uniquely identifies a method
    // in a method set.
    // See http://golang.org/ref/spec#Uniqueness_of_identifiers
    Name    string
    PkgPath string

    Type  Type  // method type
    Func  Value // func with receiver as first argument
    Index int   // index for Type.Method
}

可以看出该信息包含对象方法的名字,包路径(导出的方法,此路径为空),方法的类型,方法的接收者,该方法在对象中的位置从0开始。

通过v获取对象方法的信息。

利用v.MethodByName() Value方法:

    method := v.MethodByName("SayAge")
    fmt.Println(method)

    // 打印结果
    <*main.boy Value>

它又返回了一个Value类型。我们通过类型中的方法获取一些对象方法的信息:

    // 返回方法的地址
    fmt.Println(method.Pointer())
    // 返回方法的类型
    fmt.Println(method.Type())
    // 对象方法是否可以更改
    fmt.Println(method.CanSet())
    
    //打印结果
    4527472
        func()
    false

通过v调用方法。

注意,调用的方法必须是可导出的。

    // 有输入参数的方法调用。例如, func (this *boy) SayName (name string) {}。
    // 构造输入参数
    args := []reflect.Value{reflect.ValueOf("liming")}
    // 通过v进行调用
    v.MethodByName("SayName").Call(args)

    // 无输入参数的方法调用。例如,func (this *boy) SayName(){}。
    // 构造zero value
    args := make([]reflect.Value, 0)
    // 通过v进行调用
    v.MethodByName("SayName").Call(args)

总结

通过上面的代码,Go的反射比较方便和强大。在静态类型语言中实现反射是一件挺伟大的事。当然,这也是得益于Go有一层中间件Runtime,reflect包的大部分功能靠它来实现。上面只是列举了通过接口反射对象的方法信息以及调用方法。我们还可以加以变通来反射对象的字段信息,已经动态修改字段内容等等。总的来说,运用好reflect包对写通用型框架还是很有帮助的。在此只是抛砖引玉,还是望大神Carry,指导。

posted on 2015-04-03 10:46  rojas  阅读(871)  评论(0编辑  收藏  举报