└── README.md /README.md: -------------------------------------------------------------------------------- 1 | Go语言反射规则 - The Laws of Reflection 2 | ====================== 3 | 4 | 原文地址:[http://blog.golang.org/laws-of-reflection](http://blog.golang.org/laws-of-reflection) 5 | 6 | ##介绍 7 | 8 | 反射在计算机的概念里是指一段程序审查自身结构的能力,主要通过类型进行审查。它是元编程的一种形式,同样也是引起混乱的重大来源。 9 | 10 | 在这篇文章里我们试图阐明Go语言中的反射是如何工作的。每种语言的反射模型是不同的(许多语言不支持反射),然而本文只与Go有关,所以我们接下来所提到的“反射”都是指Go语言中的反射。 11 | 12 | 13 | ##类型与接口 14 | 15 | 由于反射是建立在类型系统(type system)上的,所以我们先来复习一下Go语言中的类型。 16 | 17 | Go是一门静态类型的语言。每个变量都有一个静态类型,类型在编译的时后被知晓并确定了下来。 18 | ```Go 19 | type MyInt int 20 | 21 | var i int 22 | var j MyInt 23 | ``` 24 | 变量`i`的类型是`int`,变量`j`的类型是`MyInt`。虽然它们有着相同的基本类型,但静态类型却不一样,在没有类型转换的情况下,它们之间无法互相赋值。 25 | 26 | 接口是一个重要的类型,它意味着一个确定的的方法集合。一个接口变量可以存储任何实现了接口的方法的具体值(除了接口本身)。一个著名的例子就是`io.Reader`和`io.Writer`: 27 | ```Go 28 | // Reader is the interface that wraps the basic Read method. 29 | type Reader interface { 30 | Read(p []byte) (n int, err error) 31 | } 32 | 33 | // Writer is the interface that wraps the basic Write method. 34 | type Writer interface { 35 | Write(p []byte) (n int, err error) 36 | } 37 | ``` 38 | 39 | 如果一个类型声明实现了`Reader`(或`Writer`)方法,那么它便实现了`io.Reader`(或`io.Writer`)。这意味着一个`io.Reader`的变量可以持有任何一个实现了`Read`方法的的类型的值。 40 | 41 | ```Go 42 | var r io.Reader 43 | r = os.Stdin 44 | r = bufio.NewReader(r) 45 | r = new(bytes.Buffer) 46 | // and so on 47 | ``` 48 | 必须要弄清楚的一点是,不管变量`r`中的具体值是什么,`r`的类型永远是`io.Reader`:Go是静态类型的,r的静态类型就是`io.Reader`。 49 | 50 | 在接口类型中有一个极为重要的例子——空接口: 51 | ```Go 52 | interface{} 53 | ``` 54 | 它表示了一个空的方法集,一切值都可以满足它,因为它们都有零值或方法。 55 | 56 | 有人说Go的接口是动态类型,这是错误的。它们都是静态类型:虽然在运行时中,接口变量存储的值也许会变,但接口变量的类型是永不会变的。我们必须精确地了解这些,因为反射与接口是密切相关的。 57 | 58 | 59 | ##深入接口 60 | 61 | Russ Cox在博客里写了一篇[详细的文章](http://research.swtch.com/2009/12/go-data-structures-interfaces.html),讲述了Go中的接口变量的意义。我们不需要列出全文,只需在这里给出一点点总结。 62 | >一个接口类型的变量里有两样东西:变量的的具体值和这个值的类型描述。更准确地来讲,这个实现了接口的值是一个基础的具体数据项,而类型描述了数据项里的所有类型。 63 | 64 | 如下所示: 65 | ```Go 66 | var r io.Reader 67 | tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) 68 | if err != nil { 69 | return nil, err 70 | } 71 | r = tty 72 | ``` 73 | 在此之后,`r`包含了`(value, type)`组合,`(tty, *os.File)`。值得注意的是,`*os.File`实现了`Read`以外的方法;虽然接口值只提供了`Read`方法,但它内置了所有的类型信息,这就是为什么我们可以么做: 74 | ```Go 75 | var w io.Writer 76 | w = r.(io.Writer) 77 | ``` 78 | 上面的所展示表达式是一个类型断言,它断言了`r`中所包含的数据项实现了`io.Writer`,所以我们可以用它对`w`赋值。在此之后,`w`将与`r`一样,包含`(tty, *os.File)`组合。接口的静态类型决定了接口变量的哪些方法会被调用,即便也许它所含的具体值有一个更大的方法集。 79 | 80 | 接下来,我们可以这么做: 81 | ```Go 82 | var empty interface{} 83 | empty = w 84 | ``` 85 | 我们的空接口变量将会在此包含同样的“组合”:`(tty, *os.File)`。这非常方便:一个空接口可以包含任何值和它的类型信息,我们可以在任何需要的时候了解它。 86 | 87 | (在这里我们无需类型断言是因为`w`已经满足了空接口。在前面的例子中我们将一个值从一个`Reader`传到了`Writer`,因为`Writer`不是`Reader`的子集,所以我们需要使用类型断言。) 88 | 89 | 这里有一个重要细节:接口里“组合”的格式永远是(值,实体类型),而不是(值,接口类型)。接口不会包含接口值。 90 | 91 | 好了,现在让我们进入反射部分。 92 | 93 | 94 | ##反射规则(一) - 从接口到反射对象 95 | 96 | 在基础上,反射是一个审查在接口变量中的`(type, value)`组合的机制。现在,我们需要了解[reflect包](https://gowalker.org/reflect)中的两个类型:`Type`和`Value`,可以让我们访问接口变量的内容。`reflect.TypeOf`函数和`reflect.ValueOf`函数返回的`reflect.Type`和`reflect.Value`可以拼凑出一个接口值。(当然,从`reflect.Value`可以很轻易地得到`reflect.Type`,但现在还是让我们把`Value`和`Type`的概念分开来看。) 97 | 98 | 我们从`TypeOf`开始: 99 | ```Go 100 | package main 101 | 102 | import ( 103 | "fmt" 104 | "reflect" 105 | ) 106 | 107 | func main() { 108 | var x float64 = 3.4 109 | fmt.Println("type:", reflect.TypeOf(x)) 110 | } 111 | ``` 112 | 这个程序打印了: 113 | ```Go 114 | type: float64 115 | ``` 116 | 117 | 看了这段代码你也许会想“接口在哪?”,这段程序里只有`float64`的变量`x`,并没有接口变量传进`reflect.TypeOf`。其实它是在这儿:在[godoc reports](https://gowalker.org/reflect/#TypeOf)的`reflect.TypeOf`的声明中包含了一个空接口: 118 | ```Go 119 | // TypeOf returns the reflection Type of the value in the interface{}. 120 | func TypeOf(i interface{}) Type 121 | ``` 122 | 123 | 当我们调用`reflect.TypeOf(x)`时,作为参数传入的`x`在此之前已被存进了一个空接口。而`reflect.TypeOf`解包了空接口,恢复了它所含的类型信息。 124 | 125 | 相对的,`reflect.ValueOf`函数则是恢复了值(从这里开始我们将修改例子并且只关注于可执行代码): 126 | ```Go 127 | var x float64 = 3.4 128 | fmt.Println("value:", reflect.ValueOf(x)) 129 | ``` 130 | 打印: 131 | ```Go 132 | value: 133 | ``` 134 | 135 | `reflect.Type`和`reflect.Value`拥有许多方法让我们可以审查和操作接口变量。一个重要的例子就是`Value`有一个`Type`方法返回`reflect.Value`的`Type`。另一个例子就是,`Type`和`Value`都有`Kind`方法,它返回一个常量,这个常量表示了被存储的元素的排列顺序:`Uint, Float64, Slice`等等。并且,`Value`的一系列方法(如`Int`或`Float`),能让我们获取被存储的值(如`int64`或`float64`): 136 | ```Go 137 | var x float64 = 3.4 138 | v := reflect.ValueOf(x) 139 | fmt.Println("type:", v.Type()) 140 | fmt.Println("kind is float64:", v.Kind() == reflect.Float64) 141 | fmt.Println("value:", v.Float()) 142 | ``` 143 | 打印: 144 | ```Go 145 | type: float64 146 | kind is float64: true 147 | value: 3.4 148 | ``` 149 | 150 | 有一些方法如`SetInt`和`SetFloat`涉及到了“可设置”(settability)的概念,这是反射规则的第三条,我们将在后面讨论。 151 | 152 | 反射库有两个特性是需要指出的。其一,为了保持API的简洁,`Value`的Getter和Setter方法是用最大的类型去操作数据:例如让所有的整型都使用`int64`表示。所以,`Value`的`Int`方法返回一个`int64`的值,`SetInt`需要传入`int64`参数;将数值转换成它的实际类型在某些时候是有必要的: 153 | ```Go 154 | var x uint8 = 'x' 155 | v := reflect.ValueOf(x) 156 | fmt.Println("type:", v.Type()) // uint8. 157 | fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true. 158 | x = uint8(v.Uint()) // v.Uint returns a uint64. 159 | ``` 160 | 161 | 其二,反射对象的`Kind`方法描述的是基础类型,而不是静态类型。如果一个反射对象包含了用户定义类型的值,如下: 162 | ```Go 163 | type MyInt int 164 | var x MyInt = 7 165 | v := reflect.ValueOf(x) 166 | ``` 167 | 虽然`x`的静态类型是`MyInt`而非`int`,但`v`的`Kind`依然是`reflect.Int`。虽然`Type`可以区分开`int`和`MyInt`,但`Kind`无法做到。 168 | 169 | 170 | ##反射规则(二) - 从反射对象到接口 171 | 172 | 如同物理学中的反射一样,Go语言的反射也是可逆的。 173 | 174 | 通过一个`reflect.Value`我们可以使用`Interface`方法恢复一个接口;这个方法将类型和值信息打包成一个接口并将其返回: 175 | ```Go 176 | // Interface returns v's value as an interface{}. 177 | func (v Value) Interface() interface{} 178 | ``` 179 | 于是我们得到一个结果: 180 | ```Go 181 | y := v.Interface().(float64) // y will have type float64. 182 | fmt.Println(y) 183 | ``` 184 | 以上代码会打印由反射对象`v`表现出的`float64`值。 185 | 186 | 然而,我们还可以做得更好。`fmt.Println`和`fmt.Printf`的参数都是通过interface{}传入的,传入之后由`fmt`的私有方法解包(就像我们前面的例子所做的一样)。正是因为`fmt`把`Interface`方法的返回结果传递给了格式化打印事务(formatted print routine),所以程序才能正确打印出`reflect.Value`的内容: 187 | ```Go 188 | fmt.Println(v.Interface()) 189 | ``` 190 | 191 | (为什么不是`fmt.Println(v)`?因为v是一个`reflect.Value`,而我们想要的是它的具体值) 192 | 由于值的类型是`float64`,我们可以用浮点格式化打印它: 193 | ```Go 194 | fmt.Printf("value is %7.1e\n", v.Interface()) 195 | ``` 196 | 并得出结果: 197 | ```Go 198 | 3.4e+00 199 | ``` 200 | 201 | 在这里我们无需对`v.Interface()`做类型断言,这个空接口值包含了具体的值的类型信息,`Printf`会恢复它。 202 | 203 | 简而言之,`Interface`方法就是`ValueOf`函数的逆,除非`ValueOf`所得结果的类型是`interface{}` 204 | 205 | 重申一遍:反射从接口中来,经过反射对象,又回到了接口中去。 206 | (Reflection goes from interface values to reflection objects and back again.) 207 | 208 | 209 | ##反射规则(三) - 若要修改反射对象,值必须可设置 210 | 211 | 第三条规则是最微妙同时也是最混乱的,但如果我们从它的基本原理开始,那么一切都不在话下。 212 | 213 | 以下的代码虽然无法运行,但值得学习: 214 | ```Go 215 | var x float64 = 3.4 216 | v := reflect.ValueOf(x) 217 | v.SetFloat(7.1) // Error: will panic. 218 | ``` 219 | 如果你运行这些代码,它会panic这些神秘信息: 220 | ```Go 221 | panic: reflect.Value.SetFloat using unaddressable value 222 | ``` 223 | 问题在于`7.1`是不可寻址的,这意味着`v`就会变得不可设置。“可设置”(settability)是`reflect.Value`的特性之一,但并非所有的`Value`都是可设置的。 224 | 225 | `Value`的`CanSet`方法返回一个布尔值,表示这个`Value`是否可设置: 226 | ```Go 227 | var x float64 = 3.4 228 | v := reflect.ValueOf(x) 229 | fmt.Println("settability of v:", v.CanSet()) 230 | ``` 231 | 打印: 232 | ```Go 233 | settability of v: false 234 | ``` 235 | 236 | 对一个不可设置的`Value`调用的`Set`方法是错误的。那么,什么是“可设置”? 237 | 238 | “可设置”和“可寻址”(addressable)有些类似,但更严格。一个反射对象可以对创建它的实际内容进行修改,这就是“可设置”。反射对象的“可设置性”由它是否拥有原项目(orginal item)所决定。 239 | 240 | 当我们这样做的时候: 241 | ```Go 242 | var x float64 = 3.4 243 | v := reflect.ValueOf(x) 244 | ``` 245 | 我们传递了一份`x`的拷贝到`reflect.ValueOf`中,所以传到`reflect.ValueOf`的接口值不是由`x`,而是由`x`的拷贝创建的。因此,如果下列语句 246 | ```Go 247 | v.SetFloat(7.1) 248 | ``` 249 | 被允许执行成功,它将不会更新`x`,即使看上去`v`是由`x`创建的。相反,它更新的是存于反射值中的`x`拷贝,`x`本身将不会受到影响。这是混乱且毫无用处的,所以这么做是非法的。“可设置”作为反射的特性之一就是为了预防这样的情况。 250 | 251 | 这虽然看起来怪异,但事实恰好相反。它实际上是一个我们很熟悉的情形,只是披上了一件不寻常的外衣。思考一下`x`是如何传递到一个函数里的: 252 | ```Go 253 | f(x) 254 | ``` 255 | 我们不会指望`f`能够修改`x`因为我们传递的是一个`x`的拷贝,而非`x`。如果我们想让`f`直接修改`x`我们必须给我们的函数传入`x`的地址(即是`x`的指针): 256 | ```Go 257 | f(&x) 258 | ``` 259 | 这是直接且熟悉的,反射的工作方式也与此相同。如果我们想用反射修改`x`,我们必须把值的指针传给反射库。 260 | 261 | 开始吧。首先我们像刚才一样初始化`x`,然后创建一个指向它的反射值,命名为`p`: 262 | ```Go 263 | var x float64 = 3.4 264 | p := reflect.ValueOf(&x) // Note: take the address of x. 265 | fmt.Println("type of p:", p.Type()) 266 | fmt.Println("settability of p:", p.CanSet()) 267 | ``` 268 | 目前的输出是: 269 | ```Go 270 | type of p: *float64 271 | settability of p: false 272 | ``` 273 | 274 | 反射对象`p`不是可设置的,但我们想要设置的不是它,而是`*p`。 275 | 为了知道`p`指向了哪,我们调用`Value`的`Elem`方法,它通过指针定向并把结果保存在了一个`Value`中,命名为`v`: 276 | ```Go 277 | v := p.Elem() 278 | fmt.Println("settability of v:", v.CanSet()) 279 | ``` 280 | 现在的`v`是一个可设置的反射对象,如下所示: 281 | ```Go 282 | settability of v: true 283 | ``` 284 | 285 | 因为它表示`x`,我们终于可以用`v.SetFloat`来修改`x`的值了: 286 | ```Go 287 | v.SetFloat(7.1) 288 | fmt.Println(v.Interface()) 289 | fmt.Println(x) 290 | ``` 291 | 正如意料中的一样: 292 | ```Go 293 | 7.1 294 | 7.1 295 | ``` 296 | 297 | 反射可能很难理解,但它所做的事正是编程语言所做的,尽管通过反射类型和值可以掩饰正在发生的事。 298 | 记住,用反射修改数据的时候需要传入它的指针哦。 299 | 300 | 301 | ##结构体 302 | 303 | 在前面的例子中,`v`并不是指针本身,它只是来源于此。 304 | 我们一般在使用反射去修改结构体字段的时候会用到。只要我们有结构体的指针,我们就可以修改它的字段。 305 | 306 | 这里有一个解析结构体变量`t`的例子。我们用结构体的地址创建了反射变量,待会儿我们要修改它。然后我们对它的类型设置了`typeOfT`,并用调用简单的方法迭代字段(详情请见[reflect包](https://gowalker.org/reflect))。 307 | 注意,我们从结构体的类型中提取了字段的名字,但每个字段本身是正常的`reflect.Value`对象。 308 | ```Go 309 | type T struct { 310 | A int 311 | B string 312 | } 313 | t := T{23, "skidoo"} 314 | s := reflect.ValueOf(&t).Elem() 315 | typeOfT := s.Type() 316 | for i := 0; i < s.NumField(); i++ { 317 | f := s.Field(i) 318 | fmt.Printf("%d: %s %s = %v\n", i, 319 | typeOfT.Field(i).Name, f.Type(), f.Interface()) 320 | } 321 | ``` 322 | 程序输出: 323 | ```Go 324 | 0: A int = 23 325 | 1: B string = skidoo 326 | ``` 327 | 328 | 关于可设置性还有一点需要介绍:`T`的字段名是大写(字段可导出/公共字段)的原因在于,结构体中只有可导出的的字段是“可设置”的。 329 | 330 | 因为`s`包含了一个可设置的反射对象,我们可以修改结构体字段: 331 | ```Go 332 | s.Field(0).SetInt(77) 333 | s.Field(1).SetString("Sunset Strip") 334 | fmt.Println("t is now", t) 335 | ``` 336 | 结果: 337 | ```Go 338 | t is now {77 Sunset Strip} 339 | ``` 340 | 341 | 如果我们修改了程序让`s`由`t`(而不是`&t`)创建,程序就会在调用`SetInt`和`SetString`的地方失败,因为`t`的字段是不可设置的。 342 | 343 | 344 | ##结论 345 | 再次列出反射法则: 346 | * 反射从接口值到反射对象中(Reflection goes from interface value to reflection object.) 347 | * 反射从反射对象到接口值中(Reflection goes from reflection object to interface value.) 348 | * 要修改反射对象,值必须是“可设置”的(To modify a reflection object, the value must be settable.) 349 | 350 | 一旦你了解反射法则,Go就会变得更加得心应手(虽然它仍旧微妙)。这是一个强大的工具,除非在绝对必要的时候,我们应该谨慎并避免使用它。 351 | 352 | 我们还有非常多的反射知识没有提及——chan的发送和接收,内存分配,使用slice和map,调用方法和函数——但是这篇文章已足够长了。我们将在以后的文章中涉及这些。 353 | 354 | *By Rob Pike* 355 | --------------------------------------------------------------------------------