Golang笔记—反射

猎奇师 · · 737 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

反射(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笔记—反射

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

737 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传