反射是程序执行时检查其所拥有的结构,尤其是类型的一种能力,这是元编程的一种形式,同时也是造成混淆的来源。
每一种语言的反射模型都不同(很多语言根本不支持反射,Python通过hasattr方法实现)Go语言中的反射通过reflect包实现,实现了运行时反射,允许程序操作任意类型的对象。
reflect包中的两个数据类Type和Value:
Type:表示一个Go类型(不是所有Go类型的Type值都能使用所有方法,可参见每个方法的文档获取使用限制,在调用有分类限定的方法时,应先使用Kind方法获知类型的分类,调用该分类不支持的方法会导致运行时的panic)。
package main
import (
"reflect"
"fmt"
)
type S interface {
Get()
Post()
}
type A struct {
Name string `json:"name"`
Value string `json:"value"`
}
func (a *A) Get() {
fmt.Println("Get is Name: ", a.Name)
}
func (a *A) Post() {
fmt.Println("Post is Name: ", a.Name)
}
func main() {
p := A{Name: "ywfuns.com", Value: "www"}
n := new(S)
k := reflect.TypeOf(p)
fmt.Println(k.Name()) // 对象名称,不含包名;即A
fmt.Println(k.String()) // 对象名称,含包名;即main.A
fmt.Println(k.PkgPath()) // 包名
fmt.Println(k.Kind()) // 对象类型,即struct
fmt.Println(k.Size()) // 存储的字节大小
fmt.Println(k.NumField()) // 获取字段数量
fmt.Println(k.Field(0).Tag) // 根据索获取字段Tag
fmt.Println(k.FieldByName("Name")) // 根据名称获取字段
fmt.Println(k.FieldByIndex([]int{0})) // 根据索引链获取嵌套字段
// 引用类型需要用Elem()获取指针所指的对象类型
h := reflect.TypeOf(n).Elem()
fmt.Println(h.NumMethod()) // 可访问的方法数(仅对接口类型统计)
fmt.Println(h.Method(0), h.Method(0).Name)
fmt.Println(h.MethodByName("A"))
}
Value: 表示类型的值(不是所有Go类型的Value值都能使用所有方法,可参见每个方法的文档获取使用限制,在调用有分类限定的方法时,应先使用Kind方法获知类型的分类,调用该分类不支持的方法会导致运行时的panic)。
案例1:
package main
import (
"reflect"
"fmt"
)
func main() {
s := "www"
val := reflect.ValueOf(s)
fmt.Println(val.String()) // 返回值
fmt.Println(val.Kind().String()) // 返回类型
s1 := reflect.ValueOf(&s) // 获取Value类型
s1.Elem().SetString("blog") // 设置值
fmt.Println(s, s1.Elem().String())
}
案例2:
package main
import (
"reflect"
"fmt"
)
type B struct {
Name string `json:"name"`
Value string `json:"value"`
}
func main() {
s := B{Name: "www", Value: "ywfuns.com"}
val := reflect.ValueOf(s)
fmt.Println(val.NumField())
fmt.Println(val.Field(0))
fmt.Println(val.FieldByName("Name"))
m := reflect.ValueOf(&s).Elem()
m.FieldByName("Name").SetString("blog")
fmt.Println(s.Name, m.Field(0))
}
案例3:
通过反射调用结构体中的方法,通过reflect.Value.Method(i int).Call()或者reflect.Value.MethodByName(name string).Call()实现
package main
import (
"fmt"
"reflect"
)
type C struct {
Name string `json:"name"`
Value string `json:"value"`
}
func (c *C) Get() {
fmt.Println("Get is", c.Name)
}
func (c *C) Set(s string) {
c.Name = s
fmt.Println("Set is ", c.Name)
}
func main() {
s := &C{Name: "www", Value: "ywfuns"}
val := reflect.ValueOf(s) // 也可以使用reflect.ValueOf(&s).Elem()
val.MethodByName("Get").Call(nil)
p := make([]reflect.Value, 1)
p[0] = reflect.ValueOf("blog")
val.MethodByName("Set").Call(p) // 通过名称调用
p[0] = reflect.ValueOf("Test")
val.Method(1).Call(p) // 通过索引调用
fmt.Println(s.Name, s.Value)
}