51

Golang笔记—反射

 5 years ago
source link: https://www.tuicool.com/articles/yUbUVzn
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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


Recommend

  • 121
    • www.lijiaocn.com 6 years ago
    • Cache

    理解golang的反射reflection

    不同语言的反射模式是不同的。 在golang的官网上有一篇文章很详细的介绍了go语言的反射机制: The Laws of Reflection。

  • 49
    • studygolang.com 6 years ago
    • Cache

    利用golang反射实现一个迷你orm

    github地址 作为静态语言,golang稍显笨拙,还好go的标准包 reflect (反射)包弥补了这点不足,它提供了一系列强大的API,能够根据执行过程中对象...

  • 59
    • studygolang.com 5 years ago
    • Cache

    Golang反射深入理解

    基本了解 在Go语言中,大多数时候值/类型/函数非常直接,要的话,定义一个。你想要个 Struct type Foo struct { A int B string } 你想要一个值,你定义出来 v...

  • 63
    • jiajunhuang.com 5 years ago
    • Cache

    Golang的反射

    反射是指,在程序运行的时候,可以动态的检查自身的类型,结构和其他信息。 要了解Go语言的反射,我们需要了解两个概念。 reflect.Type reflect.Value 我们想要在运行时知道类型和值,怎么办呢?很明显就...

  • 48
    • studygolang.com 5 years ago
    • Cache

    golang反射理解

    golang反射理论基础 反射就是动态的获取对象的信息,动态的执行对象的方法,为什么不直接获取对象的属性呢?为什么不直接调用他的方法呢?因为有时候不知道这个对象具体是什么类型,具体有哪些属性和方法。 golang中的反射和类...

  • 37
    • www.tuicool.com 5 years ago
    • Cache

    golang中反射与接口的关系

    在 golang中interface底层分析 文中分析了接口的底层原理。其中接口的内部结构分两种一种是iface接口,就是有方法的接口,另一种是eface是空接口。不管是哪种都有两个字...

  • 61
    • www.tuicool.com 4 years ago
    • Cache

    golang之反射

    反射这个概念绝大多数语言都有,比如Java,PHP之类,golang自然也不例外,反射其实程序能够自描述和自控制的一类机制。 比如,通过PHP的反射,你可以知道一个类有什么成员,有什么方法。而golang,也能够通过官方自带的reflect...

  • 44
    • segmentfault.com 4 years ago
    • Cache

    golang reflect包,反射学习与实践

    Go 语言反射的三大法则,其中包括: 从 interface{} 变量可以反射出反射对象; 从反射对象可以获取 interface{} 变量; 要修改反射对象,其值必须可设置; 从反射对象到接口值的过程就是...

  • 11
    • blog.csdn.net 3 years ago
    • Cache

    golang 用反射reflect操作结构体

    golang 用反射reflect操作结构体

  • 8
    • blog.lpflpf.cn 2 years ago
    • Cache

    Golang反射学习:手写一个RPC

    本文主要为了在对golang反射学习后做一个小练习,使用100行代码实现一个通用的RPC服务。 golang 的RPC框架还是非常丰富的,比如 gRPC,go-zero, go-dubbo 等都是使用非常普遍的rpc框架。在go语言实现的RPC客户端中,大部分RPC框架采用的是使用生成代码...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK