Golang笔记—反射

作者: 奶爸撸代码 | 来源:发表于2019-08-25 22:56 被阅读0次

    反射(Reflection)

    为什么需要反射

    有时候需要知道未知类型的类型表达方式, 有时候需要获取类型信息, 进行判断进行不同的处理

    reflect.Typereflect.Value

    reflect包中两个重要的类型.

    • reflect.Type是一个接口, 表示一个Go类型
    • 可由reflect.TypeOf()reflect.Type的类型返回某个interface{}的动态类型信息
    t := reflect.TypeOf(3)  // t: a reflect.Type
    fmt.Println(t.String()) // "int"
    // reflect.Type满足fmt.Stringer接口
    fmt.Println(t)          // "int"
    
    var w io.Writer = os.Stdout
    fmt.Println(reflect.TypeOf(w)) // "*os.File" not io.Writer
    
    • reflect.Value 可装载任意类型的值, 满足Stringer接口, reflect.ValueOf()返回一个Value
    v := reflect.ValueOf(3) // a reflect.Value
    fmt.Println(v)          // "3"
    fmt.Printf("%v\n", v)   // "3"
    fmt.Println(v.String()) // NOTE: "<int Value>"
    
    • ValueType方法返回reflect.Type
    t := v.Type()           // a reflect.Type
    fmt.Println(t.String()) // "int"
    
    • reflect.ValueOf() 的逆操作是 reflect.Value.Interface(): 返回一个interface{} ,其值是与Value相同的具体值
    v := reflect.ValueOf(3) // a reflect.Value
    x := v.Interface()      // an interface{}
    i := x.(int)            // an int
    fmt.Printf("%d\n", i)   // "3"
    
    • 跟interface{}不同的是无需用type断言知道动态类型, 比如以下format.Any使用Kind方法,得到有限的Kind进行处理
    package format
    
    import (
        "reflect"
        "strconv"
    )
    
    // Any formats any value as a string.
    func Any(value interface{}) string {
        return formatAtom(reflect.ValueOf(value))
    }
    
    // formatAtom formats a value without inspecting its internal structure.
    func formatAtom(v reflect.Value) string {
        switch v.Kind() {
        case reflect.Invalid:
            return "invalid"
        case reflect.Int, reflect.Int8, reflect.Int16,
            reflect.Int32, reflect.Int64:
            return strconv.FormatInt(v.Int(), 10)
        case reflect.Uint, reflect.Uint8, reflect.Uint16,
            reflect.Uint32, reflect.Uint64, reflect.Uintptr:
            return strconv.FormatUint(v.Uint(), 10)
        // ...floating-point and complex cases omitted for brevity...
        case reflect.Bool:
            return strconv.FormatBool(v.Bool())
        case reflect.String:
            return strconv.Quote(v.String())
        case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
            return v.Type().String() + " 0x" +
                strconv.FormatUint(uint64(v.Pointer()), 16)
        default: // reflect.Array, reflect.Struct, reflect.Interface
            return v.Type().String() + " value"
        }
    }
    

    display, 一个递归值打印器

    聚合类型只打印了类型, 引用类型打印地址, 需要进一步精细化处理

    Display("e", expr)
    
    func Display(name string, x interface{}) {
        fmt.Printf("Display %s (%T):\n", name, x)
        display(name, reflect.ValueOf(x))
    }
    
    func display(path string, v reflect.Value) {
        switch v.Kind() {
        case reflect.Invalid:
            fmt.Printf("%s = invalid\n", path)
        case reflect.Slice, reflect.Array:
            for i := 0; i < v.Len(); i++ {
                display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))
            }
        case reflect.Struct:
            for i := 0; i < v.NumField(); i++ {
                fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name)
                display(fieldPath, v.Field(i))
            }
        case reflect.Map:
            for _, key := range v.MapKeys() {
                display(fmt.Sprintf("%s[%s]", path,
                    formatAtom(key)), v.MapIndex(key))
            }
        case reflect.Ptr:
            if v.IsNil() {
                fmt.Printf("%s = nil\n", path)
            } else {
                display(fmt.Sprintf("(*%s)", path), v.Elem())
            }
        case reflect.Interface:
            if v.IsNil() {
                fmt.Printf("%s = nil\n", path)
            } else {
                fmt.Printf("%s.type = %s\n", path, v.Elem().Type())
                display(path+".value", v.Elem())
            }
        default: // basic types, channels, funcs
            fmt.Printf("%s = %s\n", path, formatAtom(v))
        }
    }
    
    • SliceArray: Len()返回数组元素个数, Index()返回元素的reflect.Value,越界会panic
    • Struct: NumField返回结构体字段个数, Field(i)返回第i字段reflect.Value形式的值
    • Map: MapKeys返回一个reflect.Value类型的slice, 对应map的keys, 遍历仍然是随机的,MapIndex(key)返回key对应的值Value
    • 指针:Elem返回指针指向的变量, 依然是reflect.Value类型, nil也是安全的, type此时为Invalid类型, 可用IsNil()事先判断
    • 接口: 先IsNil判断, 然后用v.Elem()获取动态值

    通过reflect.Value修改值

    有一些reflect.Values是可取地址的, 这种是可以设置其值的

    x := 2                   // value   type    variable?
    a := reflect.ValueOf(2)  // 2       int     no
    b := reflect.ValueOf(x)  // 2       int     no
    c := reflect.ValueOf(&x) // &x      *int    no
    d := c.Elem()            // 2       int     yes (x)
    fmt.Println(d.CanAddr()) // "true"
    
    px := d.Addr().Interface().(*int) // px := &x
    *px = 3                           // x = 3
    
    d.Set(reflect.ValueOf(4))
    
    • CanAddr方法来判断其是否可以被取地址
    • 通过调用可取地址的reflect.Value的reflect.Value.Set方法来更新
    • Set方法将在运行时执行和编译时进行类似的可赋值性约束的检查
    • Set方法:SetInt、SetUint、SetString和SetFloat等
    • 反射机制不能修改未导出成员
    • CanSet是用于检查对应的reflect.Value是否是可取地址并可被修改
    • reflect.Zero函数将变量v设置为零值
    x := 1
    rx := reflect.ValueOf(&x).Elem()
    rx.SetInt(2)                     // OK, x = 2
    rx.Set(reflect.ValueOf(3))       // OK, x = 3
    rx.SetString("hello")            // panic: string is not assignable to int
    rx.Set(reflect.ValueOf("hello")) // panic: string is not assignable to int
    
    var y interface{}
    ry := reflect.ValueOf(&y).Elem()
    ry.SetInt(2)                     // panic: SetInt called on interface Value
    ry.Set(reflect.ValueOf(3))       // OK, y = int(3)
    ry.SetString("hello")            // panic: SetString called on interface Value
    ry.Set(reflect.ValueOf("hello")) // OK, y = "hello"
    

    获取结构体字段标签

    reflect.TypeField()将返回一个reflect.StructField, 含有每个成员的名字、类型和可选的成员标签等信息。其中成员标签信息对应reflect.StructTag类型的字符串,并且提供了Get方法用于解析和根据特定key提取的子串

    // Unpack populates the fields of the struct pointed to by ptr
    // from the HTTP request parameters in req.
    func Unpack(req *http.Request, ptr interface{}) error {
        if err := req.ParseForm(); err != nil {
            return err
        }
    
        // Build map of fields keyed by effective name.
        fields := make(map[string]reflect.Value)
        v := reflect.ValueOf(ptr).Elem() // the struct variable
        for i := 0; i < v.NumField(); i++ {
            fieldInfo := v.Type().Field(i) // a reflect.StructField
            tag := fieldInfo.Tag           // a reflect.StructTag
            name := tag.Get("http")
            if name == "" {
                name = strings.ToLower(fieldInfo.Name)
            }
            fields[name] = v.Field(i)
        }
    
        // Update struct field for each parameter in the request.
        for name, values := range req.Form {
            f := fields[name]
            if !f.IsValid() {
                continue // ignore unrecognized HTTP parameters
            }
            for _, value := range values {
                if f.Kind() == reflect.Slice {
                    elem := reflect.New(f.Type().Elem()).Elem()
                    if err := populate(elem, value); err != nil {
                        return fmt.Errorf("%s: %v", name, err)
                    }
                    f.Set(reflect.Append(f, elem))
                } else {
                    if err := populate(f, value); err != nil {
                        return fmt.Errorf("%s: %v", name, err)
                    }
                }
            }
        }
        return nil
    }
    
    func populate(v reflect.Value, value string) error {
        switch v.Kind() {
        case reflect.String:
            v.SetString(value)
    
        case reflect.Int:
            i, err := strconv.ParseInt(value, 10, 64)
            if err != nil {
                return err
            }
            v.SetInt(i)
    
        case reflect.Bool:
            b, err := strconv.ParseBool(value)
            if err != nil {
                return err
            }
            v.SetBool(b)
    
        default:
            return fmt.Errorf("unsupported kind %s", v.Type())
        }
        return nil
    }
    

    显示一个类型的方法集

    • reflect.Typereflect.Value都提供了Method方法
    • 每次t.Method(i)调用返回reflect.Method的实例
    • 每次v.Method(i)调用都返回一个reflect.Value以表示方法值
    • 使用reflect.Value.Call方法调用一个Func类型的Value
    // Print prints the method set of the value x.
    func Print(x interface{}) {
        v := reflect.ValueOf(x)
        t := v.Type()
        fmt.Printf("type %s\n", t)
    
        for i := 0; i < v.NumMethod(); i++ {
            methType := v.Method(i).Type()
            fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name,
                strings.TrimPrefix(methType.String(), "func"))
        }
    }
    

    深度相等判断

    reflect.DeepEqual可以对两个值进行深度相等判断, 使用基础类型的==判断, 会递归复合类型

    func TestSplit(t *testing.T) {
        got := strings.Split("a:b:c", ":")
        want := []string{"a", "b", "c"};
        if !reflect.DeepEqual(got, want) { /* ... */ }
    }
    

    使用反射的忠告

    • 基于反射的代码脆弱, 运行时才会抛panic
    • 不能做静态类型检查, 太多则可能难以理解
    • 运行速度慢一到两个数量级, 测试适合使用, 性能关键路径避免使用
    • 若非真正需要, 请不要使用reflect

    Reference

    相关文章

      网友评论

        本文标题:Golang笔记—反射

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