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 的打印结果:
t
, v
实现了String() string
所对应的接口,所以可以在fmt
中打印出来。
fmt.Println(t)
fmt.Println(v)
// 打印结果
*main.boy
<*main.boy Value>
从上面的打印结果可见:
t
表示i
接口的类型为指向main
包下struct boy
的指针类型。它可以用来存储指向boy
的指针。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,指导。