美文网首页
golang 反射

golang 反射

作者: 天命_风流 | 来源:发表于2022-09-22 23:03 被阅读0次

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

    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 反射

          本文链接:https://www.haomeiwen.com/subject/dyabortx.html