golang反射与反射三法则

    反射是在golang程序运行时检查变量所具备类型的一种机制。因为反射能够得出关于变量结构的数据(即关于数据的数据),因此这也被认为是golang元编程的基础。初学反射,会感受有些“玄乎”。我这里由浅入深,尝试阐述反射内涵,并解读反射三法则(http://blog.golang.org/laws-of-reflectiongolang


0 从类型和方法理解反射内涵算法

    在基本的层面上,反射只是一个检查存储在接口变量中的类型和值的算法。使用反射机制,首先须要导入reflect包,reflect包中有两个重要类型须要了解,reflect.Typereflect.Value,这两个类型使得能够访问变量的内容。与此相关的,还有两个简单的函数,reflect.TypeOfreflect.ValueOf,能够从接口值中分别获取reflect.Typereflect.Value编程

    初学可能会认为reflect.Typereflect.Value是一种并列关系,但其实它们是一种包含关系,咱们结合一段代码来理解这段话。
ide

import (
   "fmt"
   "reflect"
)
 
func main() {
   var x float64 = 1.1
   fmt.Println("reflect.Value:", reflect.ValueOf(x))
   fmt.Println("reflect.Type:", reflect.TypeOf(x))
   v := reflect.ValueOf(x)
   fmt.Println("reflect.Type:",v.Type())
   fmt.Println("actual value:", v.Float())
   fmt.Println("kind is float64?", v.Kind() == reflect.Float64)
}

其输出为:函数

wKioL1YxopehtIxkAACegkwOzhg143.jpg

    根据程序及其结果,咱们能够发现:在go语言中,每一个值都包含两个内容:类型和实际的值。从类型角度来看,reflect.Value是一个关于<类型, 实际的值>的二元组,而reflect.Type是值的类型,两者是包含关系。从方法角度来看,reflect.TypeOf (reflect.ValueOf(x)).Type均可以返回reflect.Type(reflect.ValueOf(x)).Float能够返回实际的值(相似的方法还包括(reflect.ValueOf(x)).Int(reflect.ValueOf(x)).Bool等);(reflect.ValueOf(x)).Kind能够返回一个常量定义的类型。ui

    根据上述分析,咱们能够得出一个示意图,更为直观形象的代表值、类型、实际的值的关系。spa

wKioL1Yx5MzRtdBiAAEXPOux5p8508.jpg

    此外,golang采用静态类型机制,TypeOf返回静态类型;可是,Kind返回底层类型。咱们一样以一段代码来验证这段话。指针

import (
   "fmt"
   "reflect"
)
 
type MyInt int
 
func main() {
   var x MyInt = 1
   v := reflect.ValueOf(x)
   fmt.Println("reflect.Type:", v.Type())
   fmt.Println("kind is int?", v.Kind() == reflect.Int)
}

输出:orm

wKiom1YxoxKQQFAzAABrx5USwwo751.jpg


1 法则一:从接口值到反射对象的反射(Reflection goes from interface value toreflection object)对象

    前文所述内容其实就是从接口值到反射对象的反射,表明方法为reflect.ValueOfreflect.TypeOf。可能有人会问,接口?接口在哪呢?咱们来看一些前文提到这两个函数的声明,函数的参数是空接口,其实接口就在那里。关于golang的接口,你们能够参见个人另外一篇博文《Golang中的接口》。

func ValueOf(i interface{}) Value
func TypeOf(i interface{}) Type


2 法则二:从反射对象到接口值的反射(Reflection goes from reflection object to interface value)

    reflect.Value可使用Interface方法还原接口值;此方法能够高效地打包类型和值信息到接口表达中,并返回这个结果。方法声明:

func (v Value) Interface() interface{}

    经过反射对象 v 能够打印 float64 的表达值。

y :=v.Interface().(float64) // y 将为类型 float64。
fmt.Println(y)

    还有更为简洁的实现。fmt.Println,fmt.Printf等其余全部传递一个空接口值做为参数的函数,在 fmt包内部解包的方式就像以前的例子这样。所以正确的打印reflect.Value的内容的方法就是将Interface方法的结果进行格式化打印(formatted print routine). 

fmt.Println(v.Interface())

    为何不是fmt.Println(v)?由于v是一个 reflect.Value;这里但愿得到的是它保存的实际的值。

    咱们修改前文代码还进行验证:

func main() {
   var x float64 = 1.1
   fmt.Println("reflect.Value:", reflect.ValueOf(x))
   fmt.Println("reflect.Type:", reflect.TypeOf(x))
   v := (reflect.ValueOf(x))
   fmt.Println("reflect.Type:", v.Type())
   fmt.Println("actual value(interface):", v.Interface())
   fmt.Println("kind is float64?", v.Kind() == reflect.Float64)
}

其输出:

wKioL1YxpHGgRbc8AACqUduMpKM284.jpg

    进一步地,咱们能够修改上述关系示意图,新图更为简洁优雅:

wKiom1Yx5KyB-6Y_AAEc8sb5uKQ756.jpg


3. 为了修改反射对象,其值必须可设置(To modify a reflectionobject, the value must be settable)

    反射对象能够经过SetFloat等方法设置值,经过CanSet判断可设置性。可是这里面有坑,有些值是不可设置的。咱们仍是经过一段代码来看。

import (
    "fmt"
    "reflect"
)
 
func main() {
    var x float64 = 1.1
    v := reflect.ValueOf(x)
    fmt.Println("settability of v:",v.CanSet())
    v.SetFloat(1.2)
}

其输出

wKioL1YxpQrBY-onAAIabYSpE_Q032.jpg


    其结果代表,反射对象v是不可设置的,若是硬要设置的话,会有panic异常。

    为何不能设置呢?咱们能够从函数传参的角度来思考这个问题。V := reflect.ValueOf(x),这个函数是值传递,即传递了一个x的副本到函数中,而非x自己。咱们都知道,值传递的参数是不能被真正修改的。

    我最初还有过这样的想法:vx又不是一个变量,x不能被修改,可是v应该能够被修改啊。彻底从形式上考虑,这样彷佛有道理。可是再多想一层,若是容许执行,虽然v能够被修改,可是却没法更新x。也就是说,在反射值内部容许修改x的副本,可是x自己却不会受到这个影响。这会形成混乱,而且毫无心义,所以在golang中这样操做是非法的。

    让咱们从新用函数传参的角度思考这个问题。若是传递副本不能修改,那咱们就经过就传递指针好了。咱们来试试:

func main() {
    var x float64 = 1.1
    p := reflect.ValueOf(&x)
    fmt.Println("type of p:",p.Type())
    fmt.Println("settability of p:",p.CanSet())
}

wKiom1YxpTOB-8JJAAB6QfJi1gA741.jpg

    仍是不行。由于p的实际类型是*float64,而非float64,这样修改至关于要直接修改地址了。

    咱们能够借助Elem方法,经过指针来修改指针指向的具体值。

func (v Value)Elem() Value
//Elem returns the value that the interface v contains or that the pointer vpoints to. It panics if v's Kind is not Interface or Ptr. It returns the zeroValue if v is nil.
func main() {
    var x float64 = 1.1
    p := reflect.ValueOf(&x)
    fmt.Println("type of p:",p.Type())
    v := p.Elem()
    fmt.Println("type of v:",v.Type())
    fmt.Println("settability of v:",v.CanSet())
}

其输出

wKioL1YxpeaQJjobAACWdkY8w_E223.jpg

      这样就能够进行修改了。虽然p是不可修改的,可是v能够修改。这种方法思路上相似引用传参,传入地址,修改地址所指向的具体值。


参考

http://blog.golang.org/laws-of-reflection

http://www.tuicool.com/articles/VFj6ze

http://studygolang.com/articles/1468

相关文章
相关标签/搜索