zoukankan      html  css  js  c++  java
  • 小白学标准库之反射 reflect


    1. 反射简介

    反射是 元编程 概念下的一种形式,它在运行时操作不同类型的对象,检查对象的类型,大小等信息,对于没有源代码的包反射尤其有用。

    设想一个场景,读取一个包中变量 a 的类型,并打印该类型的信息。可以通过 type/switch 判断如下:

    switch t := a.(type) {
        case int:
            fmt.Println(t)
        case float64:
            fmt.Printf(t)
        ...
    }
    

    很快发现一个问题,通过 type switch 判段变量类型会有问题:

    1. 由于不知道变量的类型,所以 case 里需要写很多类型以获得匹配项。
    2. 如果类型是自定义结构体,case 无法提前预知该结构体,从而匹配不到自定义结构体。

    这是 Go 的语法元素较少,设计简单导致没有特别强的表达能力,而通过反射 reflect 可以弥补,增强操作类型对象的表达能力。

    2. 反射三大法则

    反射中最重要的函数莫过于 reflect.TypeOf 和 reflect.ValueOf,它们对应的类型分别是 reflect.Type 和 reflect.Value:

    // reflect.TypeOf, reflect.Type
    func TypeOf(i interface{}) Type {
    	eface := *(*emptyInterface)(unsafe.Pointer(&i))
    	return toType(eface.typ)
    }
    
    // reflect.ValueOf, reflect.Value
    func ValueOf(i interface{}) Value {
    	if i == nil {
    		return Value{}
    	}
    
    	escapes(i)
    
    	return unpackEface(i)
    }
    

    reflect.TypeOf 和 reflect.ValueOf 函数的作用是什么,这在标准库里就有描述,直接摘录如下:

    // TypeOf returns the reflection Type that represents the dynamic type of i.
    // If i is a nil interface value, TypeOf returns nil.
    func TypeOf(i interface{}) Type {...}
    
    
    // ValueOf returns a new Value initialized to the concrete value
    // stored in the interface i. ValueOf(nil) returns the zero Value.
    func ValueOf(i interface{}) Value {...}
    

    通过一段代码示例进一步了解 TypeOf 和 ValueOf:

    p := person{"chunqiu", 1}
    
    v := reflect.ValueOf(p)
    fmt.Println(v)
    
    t := reflect.TypeOf(p)
    fmt.Println(t)
    
    // result:
    {chunqiu 1}
    main.person
    

    可以看到 TypeOf 返回反射对象的类型,ValueOf 返回反射对象的值。

    为什么涉及到反射对象呢?这里实际上做了两次“转换”,第一次传入 reflect.TypeOf 和 reflect.ValueOf 时从具体类型转换为 interface{} 变量,接着从 interface{} 变量反射出反射对象。在反射机制下,逆着转换也是可以的,也就是从反射对象转换为 interface{} 变量。

    进一步的介绍反射的三大法则,后续介绍皆围绕着三大法则进行:

    1. 从 interface{} 变量可以反射出反射对象。
    2. 从反射对象可以反射到 interface{} 变量。
    3. 修改反射对象,其值必须可设置。

    2.1 反射法则一

    从 interface{} 变量可以反射出反射对象。上例中代码即是这一情况。以 TypeOf 举例:

    func TypeOf(i interface{}) Type {
    	eface := *(*emptyInterface)(unsafe.Pointer(&i))
    	return toType(eface.typ)
    }
    

    函数传参将外部传入的结构体类型变量转换为空接口类型变量 i,通过 unsafe.Pointer 指针转换指针到 emptyInterface 指针,最后解引用得到空接口类型的变量 eface,返回变量的类型 eface.typ。

    这一过程需要介绍的有 unsafe.Pointer 指针和 emptyInterface 结构体。

    2.1.1 unsafe.Pointer

    顾名思义 unsafe.Pointer 指针是个不安全的指针,类似于 C 中的 void * 。它可以指向任何类型的变量,并且与 uintptr 结合可对内存数据进行直接运算,当然这种操作是很危险,也是官方不推荐的。

    除了直接操作内存数据外,unsafe.Pointer 还可以强制转换类型的指针到特定类型的指针,如:

    var i int = 1
    
    var pi *int = &i
    fmt.Printf("&pi: %v, pi: %v, *pi: %v\n", &pi, pi, *pi)
    var pf *float64 = (*float64)(unsafe.Pointer(pi))
    fmt.Printf("&pf: %v, pf: %v, *pf: %v\n", &pf, pf, *pf)
    
    *pf = *pf * 10
    fmt.Printf("f: %d\n", i)
    
    // result
    &pi: 0xc000006028, pi: 0xc000014088, *pi: 1
    &pf: 0xc000006038, pf: 0xc000014088, *pf: 5e-324
    f: 10
    

    unsafe.Pointer 指针接受的是指针变量 pi 的值,也就是整型变量 i 的地址值。该指针变量(pi) 通过 unsafe.Pointer 这一座桥被转换为 *float64 类型的指针变量 pf,此时同 pi 一样,pf 也指向了变量 i 的地址值。修改 *pf 等于修改变量 i 的值。

    在这里有两点要注意的是:

    1. 变量 pi 实际可以省略,可直接向 unsafe.Pointer 传变量 i 的地址 &i。
    2. 不能像 unsafe.Pointer 传递值,编译器会报错。

    2.1.2 emptyInterface

    emptyInterface 是 interface{} 类型的实际结构体表示,可在源码中查看 emptyInterface 的定义:

    // emptyInterface is the header for an interface{} value.
    type emptyInterface struct {
    	typ  *rtype
    	word unsafe.Pointer
    }
    

    其中,rtype 用于表示变量的类型,word 是个 unsafe.Pointer 指针,它指向内部封装的数据。

    再回到 eface := *(*emptyInterface)(unsafe.Pointer(&i)) 这里。interface{} 变量被转换成内部的 emptyInterface 表示,然后从中获取相应的类型信息。

    前面介绍了 TypeOf 函数获得类型信息的实现,进一步看 ValueOf 函数实现:

    func ValueOf(i interface{}) Value {
    	if i == nil {
    		return Value{}
    	}
    
    	escapes(i)
    
    	return unpackEface(i)
    }
    

    ValueOf 首先调用 escapes 保证当前值逃逸到堆上,然后在 unpackEface 函数调用 *(*emptyInterface)(unsafe.Pointer(&i)) 将空接口变量转换成内部的 emptyInterface 表示,再获取相应的类型信息。

    对于 Printf 等函数打印也是用到了反射机制来识别传入变量的参数,类型等信息的。比如:

    v := reflect.ValueOf(p)
    fmt.Println(v)
    
    // result:
    {chunqiu 1}
    

    v 是返回的 Value 类型的结构体,其结构体表示为:

    type Value struct {
    	typ *rtype
    
    	ptr unsafe.Pointer
    
    	flag
    }
    

    通过 Println 打印的是反射对象的值。而不是 Value 的值表示。举个例子,如下结构体打印:

    type ValueTest struct {
        typ  *int
        word *int
        flag bool
    }
    
    var x int = 1
    vtest := ValueTest{&x, &x, true}
    fmt.Println(vtest)
    
    // result
    {0xc000014088 0xc000014088 true}
    

    针对不同类型结构体 Println 打印不同信息。

    2.1.3 逃逸分析

    针对上例说的 escapes 设置变量逃逸到堆上,有必要展开说明。详细了解可看 Go 逃逸分析

    这里对变量逃逸做一个总结,并对其中栈空间不足这种逃逸情况做个实践。

    变量逃逸的四大情况:

    1. 指针逃逸;
    2. interface{} 动态类型逃逸。(前面的 escapes 即是设置 interface{} 变量逃逸到堆上),为什么这么设置看 escapes 函数注释即可明白:
       // TODO: Maybe allow contents of a Value to live on the stack.
       // For now we make the contents always escape to the heap. It
       // makes life easier in a few places (see chanrecv/mapassign
       // comment below).
       escapes(i)
      
    3. 栈空间不足;
    4. 闭包;

    2.1.4 栈空间不足

    查看机器上栈允许占用的内存大小:

    $ ulimit -a
    stack size                  (kbytes, -s) 8192
    cpu time                   (seconds, -t) unlimited
    ...
    

    验证是否超过一定大小的局部变量将逃逸到堆上:

    func generate8191() {
            nums := make([]int, 8191) // < 64KB
            for i := 0; i < 8191; i++ { nums[i] = 0 }
    }
    
    func generate8192() {
            nums := make([]int, 8192) // = 64KB
            for i := 0; i < 8192; i++ { nums[i] = 0 }
    
    }
    
    func generate(n int) {
            nums := make([]int, n) // 不确定大小
            for i := 0; i < n; i++ { nums[i] = 0 }
    }
    
    func main() {
        generate8191()
        generate8192()
        generate(1)
    }
    

    编译上述代码:

    # go build -gcflags=-m main_stack.go
    # command-line-arguments
    ./main_stack.go:4:6: can inline generate8191
    ./main_stack.go:9:6: can inline generate8192
    ./main_stack.go:15:6: can inline generate
    ./main_stack.go:20:6: can inline main
    ./main_stack.go:21:17: inlining call to generate8191
    ./main_stack.go:22:17: inlining call to generate8192
    ./main_stack.go:23:13: inlining call to generate
    ./main_stack.go:5:14: make([]int, 8191) does not escape
    ./main_stack.go:10:14: make([]int, 8192) escapes to heap
    ./main_stack.go:16:14: make([]int, n) escapes to heap
    ./main_stack.go:21:17: make([]int, 8191) does not escape
    ./main_stack.go:22:17: make([]int, 8192) escapes to heap
    ./main_stack.go:23:13: make([]int, n) escapes to heap
    

    可以看到当切片内存超过或达到栈内存限制大小时将逃逸到堆上,对于不确定切片长度的对象也将逃逸到堆上。具体分析可见文章 Go 内存逃逸

    2.2 反射法则二

    反射的第二法则是从反射对象反射回接口 interface{} 变量。反射回 interface{} 变量可进一步还原成变量最原始的状态。当然,对于原始状态是 interface{} 类型的,就不需要反射成变量最原始状态这一步了。

    首先,从反射对象反射回接口 interface{} 变量:

    p := person{"chunqiu", 27, true, false}
    
    pi := reflect.ValueOf(p).Interface()
    
    fmt.Printf("pi interface: %v, pi type: %T\n", pi, pi)
    
    if v, ok := pi.(person); ok {
        fmt.Printf("pi is the value of interface{}: %v\n", v)
    }
    
    // result
    pi interface: {chunqiu 27 true false}, pi type: main.person
    pi is the value of interface{}: {chunqiu 27 true false}
    

    分开看:
    reflect.ValueOf(p) 将原始状态转换为 interface{} 类型值,接着反射为反射对象。
    reflect.ValueOf(p).Interface{} 将反射对象转换为 Interface{} 类型的值。

    接着将 interface{} 类型的值转换为原始状态 person 结构体的值:

    fmt.Printf("person struct: %v, person type: %T\n", reflect.ValueOf(p).Interface().(person), reflect.ValueOf(p).Interface().(person))
    
    // result
    person struct: {chunqiu 27 true false}, person type: main.person
    

    虽然打印结果一样,但实际上 reflect.ValueOf(p).Interface().(person) 已经是原始状态 person 结构体的值而不是接口 interface{} 的值了,可以通过接口断言来验证这一点:

    /*
        if v, ok := reflect.ValueOf(p).Interface().(person).(p); ok {
            fmt.Printf("reflect.ValueOf(p).Interface().(person) is a interface: %v\n", v)
        }
    */
    
    // invalid type assertion: reflect.ValueOf(p).Interface().(person).(p) (non-interface type person on left)
    

    实际上法则二是法则一的逆过程,为什么可以这样可以通过 Interface() 函数继续分析,这里不详细展开了:

    // Interface returns v's current value as an interface{}.
    // It is equivalent to:
    //	var i interface{} = (v's underlying value)
    // It panics if the Value was obtained by accessing
    // unexported struct fields.
    func (v Value) Interface() (i interface{}) {
    	return valueInterface(v, true)
    }
    

    2.3 反射法则三

    法则三是修改反射对象,其值必须可设置。举例如下:

    type MyInt int
    var m MyInt = 5
    
    reflect.ValueOf(m).SetInt(43)
    fmt.Println(m)
    
    // result
    panic: reflect: reflect.Value.SetInt using unaddressable value
    

    运行出错,提示 using unaddressable value。这是因为传入 ValueOf 的是值,值会在赋值给函数参数的时候进行拷贝,所以在 ValueOf 内做的操作和传入的变量没有关系。需要传地址给 ValueOf:

    reflect.ValueOf(&m).Elem().SetInt(43)
    fmt.Println(m)
    
    // result
    43
    

    与前面不同的是这里使用了 Elem 函数,Elem 函数会获取指针指向的变量,在调用 SetInt 更新变量的值。看起来复杂,其过程等价于:

    m := (*int)(unsafe.Pointer(i))
    *m = 43
    

    3. 反射对象方法

    通过 TypeOf 和 ValueOf 反射的对象可调用相应的反射对象方法获得反射对象信息。

    方法很多,可从源码中查看所需的方法。这里主要介绍 Value 的 Kind 方法,Kind 方法在源码中大量使用,比如自定义结构体,对于源码包来说并不关心它的类型是哪种自定义类型,而是关心传入值是结构体 struct, 接口 interface,指针还是其它类型。通过 Kind 方法即可以查找相关变量的底层类型。

    如 type 自定义 int 类型:

    type MyInt int
    var m MyInt = 5
    
    t := reflect.ValueOf(m).Kind()
    fmt.Println(t)
    
    // result
    int
    

    Kind 返回 m 的底层类型 int。同理,对于 struct 和指针也可使用 Kind 获得变量的底层类型:

    var age int = 1
    var p *int = &age
    fmt.Println(reflect.ValueOf(p).Kind())
    
    n := name{age: 1}
    fmt.Println(reflect.ValueOf(n).Kind())
    
    // result
    ptr
    struct
    

    芝兰生于空谷,不以无人而不芳。
  • 相关阅读:
    ArcGIS for Android示例解析之高亮要素-----HighlightFeatures
    ArcGIS Runtime for Android开发教程V2.0(8)基础篇-----地图事件
    ArcGIS for Android示例解析之空间查询-----QueryTask
    ArcGIS for Android地图控件的5大常见操作
    ArcGIS Runtime for Android开发教程V2.0(4)基础篇---MapView
    ArcGIS for Android 中MapView的地图背景设置
    使用Arcglobe 10与3dmax建立三维城市
    使用PowerDesigner建立数据库模型
    一步一步学Remoting系列文章
    App集成支付宝
  • 原文地址:https://www.cnblogs.com/xingzheanan/p/15614447.html
Copyright © 2011-2022 走看看