golang 反射

反射可以获取运行时的数据,在实际编程中,如果我们不清楚输入参数(和输出参数)的类型时,就可以考虑使用反射

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() 也是可以的

注意

即使反射可以帮我们解决很多问题,但是依然不建议使用,如果一定要使用,需要注意一些事情

  1. 使用 TypeOf()、ValueOf() 作为进入反射世界的入口,使用 val.Interface() 作为反射世界的出口
  2. 确定你在操作什么 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

设置字段的值

  1. 设置字段前需要使用 xxx.CanSet() 来确认该值是否可以设置
  2. 如果想要通过反射修改传入变量的某个字段的值,需要传入指针
  3. 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
//小红

你可能感兴趣的:(golang 反射)