在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
原文地址:http://blog.golang.org/laws-of-reflection ##介绍 反射在计算机的概念里是指一段程序审查自身结构的能力,主要通过类型进行审查。它是元编程的一种形式,同样也是引起混乱的重大来源。 在这篇文章里我们试图阐明Go语言中的反射是如何工作的。每种语言的反射模型是不同的(许多语言不支持反射),然而本文只与Go有关,所以我们接下来所提到的“反射”都是指Go语言中的反射。 ##类型与接口 由于反射是建立在类型系统(type system)上的,所以我们先来复习一下Go语言中的类型。 Go是一门静态类型的语言。每个变量都有一个静态类型,类型在编译的时后被知晓并确定了下来。 type MyInt int var i int var j MyInt 变量 接口是一个重要的类型,它意味着一个确定的的方法集合。一个接口变量可以存储任何实现了接口的方法的具体值(除了接口本身)。一个著名的例子就是 // Reader is the interface that wraps the basic Read method. type Reader interface { Read(p []byte) (n int, err error) } // Writer is the interface that wraps the basic Write method. type Writer interface { Write(p []byte) (n int, err error) } 如果一个类型声明实现了 var r io.Reader r = os.Stdin r = bufio.NewReader(r) r = new(bytes.Buffer) // and so on 必须要弄清楚的一点是,不管变量 在接口类型中有一个极为重要的例子——空接口: interface{}
它表示了一个空的方法集,一切值都可以满足它,因为它们都有零值或方法。 有人说Go的接口是动态类型,这是错误的。它们都是静态类型:虽然在运行时中,接口变量存储的值也许会变,但接口变量的类型是永不会变的。我们必须精确地了解这些,因为反射与接口是密切相关的。 ##深入接口 Russ Cox在博客里写了一篇详细的文章,讲述了Go中的接口变量的意义。我们不需要列出全文,只需在这里给出一点点总结。
如下所示: var r io.Reader tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } r = tty 在此之后, var w io.Writer w = r.(io.Writer) 上面的所展示表达式是一个类型断言,它断言了 接下来,我们可以这么做: var empty interface{} empty = w 我们的空接口变量将会在此包含同样的“组合”: (在这里我们无需类型断言是因为 这里有一个重要细节:接口里“组合”的格式永远是(值,实体类型),而不是(值,接口类型)。接口不会包含接口值。 好了,现在让我们进入反射部分。 ##反射规则(一) - 从接口到反射对象 在基础上,反射是一个审查在接口变量中的 我们从 package main import ( "fmt" "reflect" ) func main() { var x float64 = 3.4 fmt.Println("type:", reflect.TypeOf(x)) } 这个程序打印了: type: float64 看了这段代码你也许会想“接口在哪?”,这段程序里只有 // TypeOf returns the reflection Type of the value in the interface{}. func TypeOf(i interface{}) Type 当我们调用 相对的, var x float64 = 3.4 fmt.Println("value:", reflect.ValueOf(x)) 打印: value: <float64 Value>
var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("value:", v.Float()) 打印: type: float64 kind is float64: true value: 3.4 有一些方法如 反射库有两个特性是需要指出的。其一,为了保持API的简洁, var x uint8 = 'x' v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) // uint8. fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true. x = uint8(v.Uint()) // v.Uint returns a uint64. 其二,反射对象的 type MyInt int var x MyInt = 7 v := reflect.ValueOf(x) 虽然 ##反射规则(二) - 从反射对象到接口 如同物理学中的反射一样,Go语言的反射也是可逆的。 通过一个 // Interface returns v's value as an interface{}. func (v Value) Interface() interface{} 于是我们得到一个结果: y := v.Interface().(float64) // y will have type float64. fmt.Println(y) 以上代码会打印由反射对象 然而,我们还可以做得更好。 fmt.Println(v.Interface()) (为什么不是 fmt.Printf("value is %7.1e\n", v.Interface()) 并得出结果: 3.4e+00
在这里我们无需对 简而言之, 重申一遍:反射从接口中来,经过反射对象,又回到了接口中去。 (Reflection goes from interface values to reflection objects and back again.) ##反射规则(三) - 若要修改反射对象,值必须可设置 第三条规则是最微妙同时也是最混乱的,但如果我们从它的基本原理开始,那么一切都不在话下。 以下的代码虽然无法运行,但值得学习: var x float64 = 3.4 v := reflect.ValueOf(x) v.SetFloat(7.1) // Error: will panic. 如果你运行这些代码,它会panic这些神秘信息: panic: reflect.Value.SetFloat using unaddressable value
问题在于
var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("settability of v:", v.CanSet()) 打印: settability of v: false
对一个不可设置的 “可设置”和“可寻址”(addressable)有些类似,但更严格。一个反射对象可以对创建它的实际内容进行修改,这就是“可设置”。反射对象的“可设置性”由它是否拥有原项目(orginal item)所决定。 当我们这样做的时候: var x float64 = 3.4 v := reflect.ValueOf(x) 我们传递了一份 v.SetFloat(7.1)
被允许执行成功,它将不会更新 这虽然看起来怪异,但事实恰好相反。它实际上是一个我们很熟悉的情形,只是披上了一件不寻常的外衣。思考一下 f(x) 我们不会指望 f(&x) 这是直接且熟悉的,反射的工作方式也与此相同。如果我们想用反射修改 开始吧。首先我们像刚才一样初始化 var x float64 = 3.4 p := reflect.ValueOf(&x) // Note: take the address of x. fmt.Println("type of p:", p.Type()) fmt.Println("settability of p:", p.CanSet()) 目前的输出是: type of p: *float64 settability of p: false 反射对象 v := p.Elem() fmt.Println("settability of v:", v.CanSet()) 现在的 settability of v: true
因为它表示 v.SetFloat(7.1) fmt.Println(v.Interface()) fmt.Println(x) 正如意料中的一样: 7.1 7.1 反射可能很难理解,但它所做的事正是编程语言所做的,尽管通过反射类型和值可以掩饰正在发生的事。 记住,用反射修改数据的时候需要传入它的指针哦。 ##结构体 在前面的例子中, 这里有一个解析结构体变量 type T struct { A int B string } t := T{23, "skidoo"} s := reflect.ValueOf(&t).Elem() typeOfT := s.Type() for i := 0; i < s.NumField(); i++ { f := s.Field(i) fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(), f.Interface()) } 程序输出: 0: A int = 23 1: B string = skidoo 关于可设置性还有一点需要介绍: 因为 s.Field(0).SetInt(77) s.Field(1).SetString("Sunset Strip") fmt.Println("t is now", t) 结果: t is now {77 Sunset Strip} 如果我们修改了程序让 ##结论 再次列出反射法则:
一旦你了解反射法则,Go就会变得更加得心应手(虽然它仍旧微妙)。这是一个强大的工具,除非在绝对必要的时候,我们应该谨慎并避免使用它。 我们还有非常多的反射知识没有提及——chan的发送和接收,内存分配,使用slice和map,调用方法和函数——但是这篇文章已足够长了。我们将在以后的文章中涉及这些。 By Rob Pike |
请发表评论