反射可以获取运行时的数据,在实际编程中,如果我们不清楚输入参数(和输出参数)的类型时,就可以考虑使用反射
Type、Value 和 Kind
在 reflect
包中,使用 Type 和 Value 表示参数的类型和值,使用 Kind 表示 Type 的特定类型。
在反射的世界中,我们所有的操作都围绕这三个概念展开
func TestReflect(t *testing.T) {
s := "hello world"
typ := reflect.TypeOf(s)
val := reflect.ValueOf(s)
kind := typ.Kind()
fmt.Println("typ:", typ)
fmt.Println("val:", val)
fmt.Println("kind:", kind)
}
//typ: string
//val: hello world
//kind: string
由于我们可以通过 Value 确定 Type, 所以使用 typ := val.Type() 也是可以的
注意
即使反射可以帮我们解决很多问题,但是依然不建议使用,如果一定要使用,需要注意一些事情
- 使用 TypeOf()、ValueOf() 作为进入反射世界的入口,使用 val.Interface() 作为反射世界的出口
- 确定你在操作什么 Kind 的 Type 和 Value,并在调用方法前仔细阅读文档,否则很容易出现 panic
使用
反射可以应用于所有数据类型中,但最常用的是 struct,所以这里只展示对 struct 的用法
获取字段
type User struct {
Name string
Age uint64
Addr string
}
func TestField(t *testing.T) {
u := User{
Name: "小明",
Age: 18,
Addr: "银河系",
}
typ := reflect.TypeOf(u)
n := typ.NumField()
for i := 0; i < n; i++ {
field := typ.Field(i)
fmt.Println(field.Name)
}
}
//Name
//Age
//Addr
获取字段的值
func TestFieldValue(t *testing.T) {
u := User{
Name: "小明",
Age: 18,
Addr: "银河系",
}
val := reflect.ValueOf(u)
typ := val.Type() // 可以通过 Value 获取 Type
n := typ.NumField()
for i := 0; i < n; i++ {
f := typ.Field(i)
v := val.Field(i)
fmt.Println(f.Name, "-->", v)
}
}
//Name --> 小明
//Age --> 18
//Addr --> 银河系
获取 tag
type User struct {
Name string `json:"name"`
Age uint64 `json:"age"`
Addr string `json:"addr"`
}
func TestTag(t *testing.T) {
u := User{
Name: "小明",
Age: 18,
Addr: "银河系",
}
typ := reflect.TypeOf(u)
n := typ.NumField()
for i := 0; i < n; i++ {
f := typ.Field(i)
tag, ok := f.Tag.Lookup("json")
fmt.Println(f.Name, " json -->", tag, ",", ok)
tag, ok = f.Tag.Lookup("xml")
fmt.Println(f.Name, " xml -->", tag, ",", ok)
}
}
//Name json --> name , true
//Name xml --> , false
//Age json --> age , true
//Age xml --> , false
//Addr json --> addr , true
//Addr xml --> , false
设置字段的值
- 设置字段前需要使用 xxx.CanSet() 来确认该值是否可以设置
- 如果想要通过反射修改传入变量的某个字段的值,需要传入指针
- xxx.Elem() 可以获取指针指向的值 或 接口的实现类
func TestSetField(t *testing.T) {
u := User{
Name: "小明",
Age: 18,
Addr: "银河系",
}
val := reflect.ValueOf(u)
fmt.Printf("类型:%s\n", val.Type().Name())
fmt.Printf("val能否设置:%v\n", val.CanSet())
fmt.Printf("val字段能否设置:%v\n", val.Field(0).CanSet())
fmt.Println("")
val = reflect.ValueOf(&u)
fmt.Printf("类型:%s\n", val.Type())
fmt.Printf("val能否设置:%v\n", val.CanSet())
// 获取指针指向的值
val = val.Elem()
fmt.Printf("val字段能否设置:%v\n", val.Field(0).CanSet())
val.Field(0).Set(reflect.ValueOf("小红"))
fmt.Println("")
fmt.Println(u.Name)
}
//类型:User
//val能否设置:false
//val字段能否设置:false
//
//类型:*ref.User
//val能否设置:false
//val字段能否设置:true
//
//小红
方法调用
对方法的调用,要注意接收器为指针还是值,指针接收器可以调用值方法,而值方法无法调用指针方法
// 注意这里是值接收器
func (u User) GetName() string {
fmt.Println("调用 GetName")
return u.Name
}
// 注意这里是指针接收器
func (u *User) SetName(name string) {
u.Name = name
fmt.Println("调用 SetName")
}
func TestCallMethod(t *testing.T) {
u := User{
Name: "小明",
Age: 18,
Addr: "银河系",
}
// 你可以通过 Value 调用方法
// 注意这里是取地址
v1 := reflect.ValueOf(&u)
n1 := v1.NumMethod()
fmt.Printf("有 %d 个方法可以调用\n", n1)
me := v1.MethodByName("SetName")
me.Call([]reflect.Value{reflect.ValueOf("小红")})
fmt.Println(u.GetName())
fmt.Println("")
// 这里是取值
v2 := reflect.ValueOf(u)
n2 := v2.NumMethod()
fmt.Printf("有 %d 个方法可以调用\n", n2)
fmt.Println("")
// 也可以通过 Type 调用方法
typ := reflect.TypeOf(&u)
if me2, has := typ.MethodByName("GetName"); has {
// 若用 Type 调用方法,第一个参数必须是接收器
res := me2.Func.Call([]reflect.Value{reflect.ValueOf(&u)})
for _, item := range res {
fmt.Println(item)
}
}
}
//有 2 个方法可以调用
//调用 SetName
//调用 GetName
//小红
//
//有 1 个方法可以调用
//
//调用 GetName
//小红
网友评论