反射的简单定律
前言
这篇文章大概2个月前就开始在边看边翻译了,可惜生了场病,然后不断地出问题,工作也没了,人比较焦虑。就耽搁了。
在我的初衷里,是准备对Go语言进行一个全面的系统的学习和研究的,但是目前看下来,是坚持不下去了。只得暂摆,不过这篇文章弄到一半放在这里,心里也是一个梗,就趁着最近的时间把它全部翻完,说实话这工作好累,一方面是英文的基础不扎实,再碰到大量的计算机术语,更是一道壁垒。不过总算也是坚持子下来,翻完之后,发现网上其实已经有别人做了同样的事情,撞车了。好在自己也算是学习一下知识,并不会觉得是无用功。
之后又花了一点时间,对照着别人的成果,将我自己的成果重新润了一下色,让句子读起来更通顺一点,并加了一些自己的理解。
应该说,这一篇是自己最用心翻译的文章,虽然说谈不上完美,算是对我Go语言学习(reflect部分)的一个总结。当然,这篇文章在整个Go的reflect领域里面,还是太浅,有兴趣可以看一下深入的知识。
Go语言反射之定律
The Laws of Reflection
介绍
Introduction
在计算机领域中,反射是一种主要通过types[类型],检查自身结构的编程手段。它是元编程的一种形式,同时也是引发混乱的根源[相对于程序本身而言]。 注:用来生成代码的程序有时被称为元程序(metaprogram);编写这种程序就称为元编程(metaprogramming)。
Reflection in computing is the ability of a program to examine its own structure, particularly through types; it’s a form of metaprogramming. It’s also a great source of confusion.
在这篇文章中,我们试图通过阐释GO语言中反射的运作机制,从而理清孕育于其中的概念。每个语言的反射模式都是不尽相同的(当然,也有很多语言并不支持反射),由于我们这篇文章只涉及到Go语言的内容,所以后面所指的“reflection”就仅仅意味着“reflection in Go”,即Go语言中的反射。
In this article we attempt to clarify things by explaining how reflection works in Go. Each language’s reflection model is different (and many languages don’t support it at all), but this article is about Go, so for the rest of this article the word “reflection” should be taken to mean “reflection in Go”.
类型和接口
Types and interfaces
因为反射是建立在类型系统上的,所以我们需要整理一下Go语言中的types这个概念。
Because reflection builds on the type system, let’s start with a refresher about types in Go.
Go是一种基于静态类型化的的语言。每一个变量都有一个静态类型,也即而言,在编译周期中将会确定其变量的类型:int(整形),float32(浮点),*MyType(指针), []byte(比特),诸如此类。如果我们声明
Go is statically typed. Every variable has a static type, that is, exactly one type known and fixed at compile time: int, float32, *MyType, []byte, and so on. If we declare
1 | type MyInt int //声明一个新的类型,名为MyInt,其基本类型是int类型 |
那么变量i是整数类型,变量j则是自定义的MyInt[本质上还是int]类型。变量i和j都有着一个很清晰的静态类型,以及,虽然它们有一个同样的基本类型[也就是本质上都是int类型],但它们并不能在未经过转换的情况下进行互相赋值的操作。
then i has type int and j has type MyInt. The variables i and j have distinct static types and, although they have the same underlying type, they cannot be assigned to one another without a conversion.
在众多变量类型的种类中,接口类型无疑是非常重要的一种,它能用来表示方法的固定集合。一个接口变量能存储任意的具体(非接口)数值,只要这个数值实现了接口方法。一个众所周知的双组类型的例子是io.Reader和io.Writer,此读和写两个类型源自于io包:pair 是一种模版类型。每个pair 可以存储两个值。这个单词真不好翻译,原意是对、双的意思,我这里就称之为双组类型,后面会有更多的提到
One important category of type is interface types, which represent fixed sets of methods. An interface variable can store any concrete (non-interface) value as long as that value implements the interface’s methods. A well-known pair of examples is io.Reader and io.Writer, the types Reader and Writer from the io package:
1 | // Reader is the interface that wraps the basic Read method. |
任何类型,只要是实现了读(或写)的声明方法,就意味着实现了io.Reader (或io.Writer)。此间讨论的目的,主要是昭示类型io.Reader中的变量能储存任何一个类型中含有Read方法的数值:signature直译是签名的意思,而在计算机中也多用于签名的含义,可是在这里感觉签名又有些不合文意,但找不到更好的翻译,或者可以理解为含有Reader和Writer方法的接口补充:网上有翻译成声明的,或许可以更贴近一点这里的意思,姑且就用声明来表示吧,后同。
Any type that implements a Read (or Write) method with this signature is said to implement io.Reader (or io.Writer). For the purposes of this discussion, that means that a variable of type io.Reader can hold any value whose type has a Read method:
【个人理解】
上面这一段翻出来很拗口,好在下面有一个例子可以说明上面一段话的含义,首先先声明一个变量r,r的类型是io.Reader。这就意味着只要是实现了Read()方法的对象,都可以赋值给r。比如这个os.Stdin,其代码在GO的代码库src\os\file.go文件中,在这个文件中,我们可以找到一个实现了Read()的方法:
1 | func (f *File) Read(b []byte) (n int, err error) {} |
因此r = os.Stdin就是合法的。
而看bufio.NewReader(r)的代码:
1 | // NewReader returns a new Reader whose buffer has the default size. |
这个返回值的*Reader,同样在bufio的代码之中:
1 | // Reader implements buffering for an io.Reader object. |
看注释也知道,这是基于io.Reader对象的。大抵的意思就是张三生了儿子张五,李四生了儿子李五,他们从本质上来说,都是人类。
1 | var r io.Reader |
这里非常重要的一点是你需要明白无论具体数值r如何被保存,r的类型始终是io.Reader:Go是一种静态类型化的语言,而r的静态的类型就是io.Reader。
It’s important to be clear that whatever concrete value r may hold, r’s type is always io.Reader: Go is statically typed and the static type of r is io.Reader.
一个关于接口类型的非常重要的例子就是空接口:
An extremely important example of an interface type is the empty interface:
1 | interface{} |
它表示空的方法集合,并且完全可以适用于任意数值,因为任何值都有零值或是多个方法。
It represents the empty set of methods and is satisfied by any value at all, since any value has zero or more methods.
【个人理解】
补充一个例子:
1 | var r interface{} |
输出为:hello
一些人说,Go接口是动态类型化的,那其实是具有严重误导性的、错误的。Go接口始终是静态类型化的:接口类型的变量总是不变的静态类型,即使是在整个运行周期存储在接口变量中的数值会改变其类型,但是这些数值再如何改变都还是切合于接口的。
【个人理解】
此段有些拗口,其本意就是被赋在接口变量中的值的类型永远是接口类型吧
Some people say that Go’s interfaces are dynamically typed, but that is misleading. They are statically typed: a variable of interface type always has the same static type, and even though at run time the value stored in the interface variable may change type, that value will always satisfy the interface.
我们需要对这些内容非常的清晰,因为反射和接口两者之间的关联相当紧密。
We need to be precise about all this because reflection and interfaces are closely related.
接口的陈述
The representation of an interface
外国的一个叫Russ Cox的程序员写了一篇关于Go语言中的接口数值的博客文章,非常详尽。我们并不需要再去完整地在此重复一遍,但是还是需要有条理地简单的归纳一下。
Russ Cox has written a detailed blog post about the representation of interface values in Go. It’s not necessary to repeat the full story here, but a simplified summary is in order.
1 | 注:这篇文章的地址是:http://research.swtch.com/2009/12/go-data-structures-interfaces.html |
接口类型的一个变量会存储两份数据就是上面提到的双组数据:变量的具体数值,和这个数值的类型的具体描述符。更确切一些的讲,这个数值是一个实现了接口的基础性具体数据项,并且这类型是描述了此数据项的全类型。后面举个例子
A variable of interface type stores a pair: the concrete value assigned to the variable, and that value’s type descriptor. To be more precise, the value is the underlying concrete data item that implements the interface and the type describes the full type of that item. For instance, after
1 | var r io.Reader |
简要性地来说,r包含了一份(值,类型)形式的双组数据,也即(tty,*os.File)。注意类型*os.File实现了Read以外的方法;即使接口数值只提供了读方法的入口,这个数值的内部携带了它的类型的所有信息。这就是为什么我们可以做下面这些事情:
r contains, schematically, the (value, type) pair, (tty, *os.File). Notice that the type *os.File implements methods other than Read; even though the interface value provides access only to the Read method, the value inside carries all the type information about that value. That’s why we can do things like this:
1 | var w io.Writer |
这个表达的语句是一个类型断言;它断言了变量r中的数据项实现了io.Writer接口,所以我们也能将它赋派给w。在赋派之后,w也将包含一份双组数据(tty,*os.File)。这和r所持有的双组数据是相同的。这个接口的静态类型决定接口变量中的哪个方法可以被调用,即使这个具体的数值内部有更多的方法集合。
The expression in this assignment is a type assertion; what it asserts is that the item inside r also implements io.Writer, and so we can assign it to w. After the assignment, w will contain the pair (tty, *os.File). That’s the same pair as was held in r. The static type of the interface determines what methods may be invoked with an interface variable, even though the concrete value inside may have a larger set of methods.
继续下去,我们能这样做:
Continuing, we can do this:
1 | var empty interface{} |
我们的空接口的数值是空的,将也包含同样的双组数据(tty, *os.File)。这样的方便之处在于:一个空接口能持有任何数值,能包含我们所需要的关于这个数值的任何信息。
and our empty interface value empty will again contain that same pair, (tty, *os.File). That’s handy: an empty interface can hold any value and contains all the information we could ever need about that value.
(这里我们可以不需要类型断言,因为w是已知的静态的,w完全可以满足空接口的规则。在上上个例子中,我们移动a Reader到a Writer,我们需要明确地使用类型断言,因为Writer这个方法不是Reader的子集合。)
(We don’t need a type assertion here because it’s known statically that w satisfies the empty interface. In the example where we moved a value from a Reader to a Writer, we needed to be explicit and use a type assertion because Writer’s methods are not a subset of Reader’s.)
一个重要的细节是接口中的双组数据总是以(值,具体实现类型)这样的形式呈现,而不能以(值,接口类型)的形式呈现。接口不能持有接口数值。
One important detail is that the pair inside an interface always has the form (value, concrete type) and cannot have the form (value, interface type). Interfaces do not hold interface values.
【个人理解】
这段不知道是否可以这样理解:
1 | var t interface{} |
现在我们可以开始学习反射了。
Now we’re ready to reflect.
反射的第一定律
The first law of reflection
1.反射从接口数值到接口对象
1. Reflection goes from interface value to reflection object.
从浅的地方说起,反射仅是一种检查类型和接口变量内部数值双组数据形式存诸在接口变量中的机制。开始的时候,我们知道在反射包中有两种类型:Type和Value。借助于这两个类型,我们可以访问接口变量中的内容,此外还有两个简单的方法,称之为reflect.TypeOf和reflect.ValueOf,用于从一个接口数值中分别抽取到reflect.Type和reflect.Value这两个自定义类型的数值。(同样,从reflect.Value中得到reflect.Type也是相当容易的,但是我们现在要区别并保有Value和Type两个不同的概念)
1 | //两个自定义类型位于reflect库中 |
At the basic level, reflection is just a mechanism to examine the type and value pair stored inside an interface variable. To get started, there are two types we need to know about in package reflect: Type and Value. Those two types give access to the contents of an interface variable, and two simple functions, called reflect.TypeOf and reflect.ValueOf, retrieve reflect.Type and reflect.Value pieces out of an interface value. (Also, from the reflect.Value it’s easy to get to the reflect.Type, but let’s keep the Value and Type concepts separate for now.)
让我们开始写一段关于TypeOf代码的例子:
Let’s start with TypeOf:
1 | package main |
这段程序打印出
This program prints
1 | type: float64 |
你可能惊异于此处的接口在哪里?从这段程序中看传入reflect.TypeOf的是类型为64位浮点数的变量x,而并非是接口数值。其实需要的接口确实是在这里;参考go的文档说明,reflect.TypeOf的声明中包含着一个空接口:
You might be wondering where the interface is here, since the program looks like it’s passing the float64 variable x, not an interface value, to reflect.TypeOf. But it’s there; as godoc reports[https://golang.org/pkg/reflect/#TypeOf], the signature of reflect.TypeOf includes an empty interface:
1 | // TypeOf returns the reflection Type of the value in the interface{}. |
【个人理解】
表示传入的i是一个接口类型,接口类型是万能类型,所以传任意值都可以
当我们调用reflect.TypeOf(x)方法的时候,x首先存储在一个空接口中,且后作为参数进行传递;reflect.TypeOf解包空接口并恢复其所含的类型信息。
When we call reflect.TypeOf(x), x is first stored in an empty interface, which is then passed as the argument; reflect.TypeOf unpacks that empty interface to recover the type information.
reflect.ValueOf方法,当然就是把这个数值给恢复出来(这里我们省略了一些不必要的代码部分,将专注于执行的关键代码)
The reflect.ValueOf function, of course, recovers the value (from here on we’ll elide the boilerplate and focus just on the executable code):
1 | var x float64 = 3.4 |
打印出
prints
1 | value: <float64 Value> |
(我们很明显地调用了这个String方法,因为默认情况下fmt包会直接在reflect.Value中调用并显示其中的具体数值,字符串方法并非如此。)
(We call the String method explicitly because by default the fmt package digs into a reflect.Value to show the concrete value inside. The String method does not.)
【个人理解】
不是很理解这段话的意思,不知道是不是表示
1 | var x float64 = 3.4 |
如果是这样的话,输入的结果直接就是
value: 3.4
reflect.Type和reflect.Value都有一些方法让我们可以检查和操作它们。一个很重要的例子是在Value中有一个Type的方法用来返回reflect.Value中的Type。另一个例子是在Type和Value中都有一个Kind方法用来返回存储于常量中的项是属于何种类型:Uint, Float64, Slice, 等等。同时,Value中的方法如Int和Float让我们获取被存储于其中的值(如int64和float64):sort在这里也应该是作为“种类”的意思来解释的,如同type和kind,在网上看到有种解释是排列序列,感觉完全解释不通。
Both reflect.Type and reflect.Value have lots of methods to let us examine and manipulate them. One important example is that Value has a Type method that returns the Type of a reflect.Value. Another is that both Type and Value have a Kind method that returns a constant indicating what sort of item is stored: Uint, Float64, Slice, and so on. Also methods on Value with names like Int and Float let us grab values (as int64 and float64) stored inside:
1 | var x float64 = 3.4 |
打印出
prints
1 | type: float64 |
这里也有如SetInt和SetFloat这样的Set方法,但是我们在使用他们之前需要明白可设置性的含义,这是反射的第三定律中的内容,将在后面讨论。
There are also methods like SetInt and SetFloat but to use them we need to understand settability, the subject of the third law of reflection, discussed below.
在反射库里有一组特性值得重点对待。首先,为保持API的简洁性,在“getter”和“setter”两个操作方法中都是以最大的类型定义去持有(或是操作)数值的:如int64会应用在所有的有符号整型(int8,int32)中。所以,Value中的Int方法一律返回int64类型,并且在SetInt进行赋值时也采用int64类型进行操作;这在转换成实际类型时是非常有必要的。
The reflection library has a couple of properties worth singling out. First, to keep the API simple, the “getter” and “setter” methods of Value operate on the largest type that can hold the value: int64 for all the signed integers, for instance. That is, the Int method of Value returns an int64 and the SetInt value takes an int64; it may be necessary to convert to the actual type involved:
1 | var x uint8 = 'x' |
第二个特性是反射对象的Kind是用来描述基础类型的,而不是静态类型。如果反射对象包含一个用户定义的整形类型的值,如
The second property is that the Kind of a reflection object describes the underlying type, not the static type. If a reflection object contains a value of a user-defined integer type, as in
1 | type MyInt int |
这个变量v的Kind仍旧是reflect.Int,尽管x的静态类型是MyInt,而不是int。换种说法,这Kind不能区分MyInt和int,哪怕Type是可以区分的。
the Kind of v is still reflect.Int, even though the static type of x is MyInt, not int. In other words, the Kind cannot discriminate an int from a MyInt even though the Type can.
反射的第二定律
The second law of reflection
2. 反射的进行是从反射对象到值的
2. Reflection goes from reflection object to interface value.
如同物理反射一样,Go的反射是可逆的。
Like physical reflection, reflection in Go generates its own inverse.
通过reflect.Value我们可以使用接口方法来恢复接口的数值;事实上此方法将类型和数值的信息打包成一个以接口形式展呈的包数据,并作为结果返回:
Given a reflect.Value we can recover an interface value using the Interface method; in effect the method packs the type and value information back into an interface representation and returns the result:
1 | // Interface returns v's value as an interface{}. |
因此我们可以说
As a consequence we can say
1 | y := v.Interface().(float64) // y will have type float64. |
用来打印出通过反射对象呈现出的float64的值。
to print the float64 value represented by the reflection object v.
【个人理解】
上面那个例子不太好。
1 | var x float64 = 3.4 |
这样就可以更好的理解了,fmt.Println(y)的时候,这个y已经不再具体.kind()方法了。
其实我们还可以更进一步,传入到fmt.Println,fmt.Printf等的参数都可以视作为空接口类型数值,之后它们会在fmt包的内部被解开,正如我们在上一个例子中做的一样。它可以正确地打印reflect.Value的内容,并做为接口方法的结果返回给格式化打印程序。routine在某些特定词组中有程序的意思,不知道这里是不是,否则不太好翻译了。
We can do even better, though. The arguments to fmt.Println, fmt.Printf and so on are all passed as empty interface values, which are then unpacked by the fmt package internally just as we have been doing in the previous examples. Therefore all it takes to print the contents of a reflect.Value correctly is to pass the result of the Interface method to the formatted print routine:
1 | fmt.Println(v.Interface()) |
(为什么不能写成fmt.Println(v)?因为v是一个reflect.Value;我们想持存的则是具体数值。)也因此我们的值是一个float64的类型,我们甚至能使用浮点输出格式,如果我们想的话:
(Why not fmt.Println(v)? Because v is a reflect.Value; we want the concrete value it holds.) Since our value is a float64, we can even use a floating-point format if we want:
1 | fmt.Printf("value is %7.1e\n", v.Interface()) |
会得到下面的情形
and get in this case
1 | 3.4e+00 |
再次说明,这里不需要做类型断言来判断v.Interface()的结果为float64;空接口数值内有具体数值的类型信息,Printf将会恢复它。
Again, there’s no need to type-assert the result of v.Interface() to float64; the empty interface value has the concrete value’s type information inside and Printf will recover it.
简而言之,这接口方法是函数ValueOf的反逆,除了ValueOf的结果永远是静态类型interface{}以外
In short, the Interface method is the inverse of the ValueOf function, except that its result is always of static type interface{}.
重申一次:反射是从接口数值到反射对象,然后再次回到接口数值中。
Reiterating: Reflection goes from interface values to reflection objects and back again.
反射的第三定律
The third law of reflection
3. 如要修改反射对象,他的数值必须可设置状态。
3. To modify a reflection object, the value must be settable.
这第三定律非常微妙和混乱,但是如果我们从它的基本原理开始探研的话,它仍然可以很好地被理解
The third law is the most subtle and confusing, but it’s easy enough to understand if we start from first principles.
这儿有一些无法正常运行的代码,但是值得研究一下。
Here is some code that does not work, but is worth studying.
1 | var x float64 = 3.4 |
如果你运行这段代码,它将抛出一个带有隐晦的异常消息的错误
If you run this code, it will panic with the cryptic message
1 | panic: reflect.Value.SetFloat using unaddressable value |
这个问题在于7.1这个数值并没有可设定的地址;也就是说目前的v是不可设置的。Settability可设置性是反射数值的一个特性,但并非所有的反射数值都具有这个特性。
The problem is not that the value 7.1 is not addressable; it’s that v is not settable. Settability is a property of a reflection Value, and not all reflection Values have it.
CanSet这个操作数值的方法用于表示值是否可设置;在我们的例子中,
The CanSet method of Value reports the settability of a Value; in our case,
1 | var x float64 = 3.4 |
打印
prints
1 | settability of v: false |
在一个非可设置的数值中调用Set方法是错误的。但是……说了半天,到底什么是settability呢?
It is an error to call a Set method on an non-settable Value. But what is settability?
settability有些像addressability可寻址能力,寻址率,但是更加严格。这是一种特性,一种反射对象能修改创建这个反射对象的实际的存储内容的特性。Settability是由反射对象是否持存原始项来决定的。当我们说:
Settability is a bit like addressability, but stricter. It’s the property that a reflection object can modify the actual storage that was used to create the reflection object. Settability is determined by whether the reflection object holds the original item. When we say
1 | var x float64 = 3.4 |
我们传入x的拷贝到reflect.ValueOf中,也就是x的一份拷贝传入到reflect.ValueOf中而创建了接口数值,而不是x自身。于是,假如这个陈述
we pass a copy of x to reflect.ValueOf, so the interface value created as the argument to reflect.ValueOf is a copy of x, not x itself. Thus, if the statement
1 | v.SetFloat(7.1) |
是被允许并且是成功执行的,它并非更新变量x,即使v看上去像是从变量x那里被创建的,它将更新存储在反射数值内的x的拷贝,而x自身不会受到任何影响。这将非常的混乱和无用,因此他是不合法的,settability则正是为了避免这种问题而产生的一种特性。
were allowed to succeed, it would not update x, even though v looks like it was created from x. Instead, it would update the copy of x stored inside the reflection value and x itself would be unaffected. That would be confusing and useless, so it is illegal, and settability is the property used to avoid this issue.
虽然这看上去非常奇怪,实际并非这样。这只是包了一层使人感到迷惑的外衣而已,事实上其本质还是我们所熟悉的一种程序手段。想一下在普通的函数中传入参数x:
If this seems bizarre, it’s not. It’s actually a familiar situation in unusual garb. Think of passing x to a function:
1 | f(x) |
我们将不期望函数f能修改x,因为我们传入的是x的值的一个拷贝,而不是x自己。如果我们想让f直接修改x,我们必须把x的地址传入到我们的函数中去。(其实也就是传入x的指针):
We would not expect f to be able to modify x because we passed a copy of x’s value, not x itself. If we want f to modify x directly we must pass our function the address of x (that is, a pointer to x):
1 | f(&x) |
这种原理是多么地直截了当和令人熟悉,反射亦是同样的工作原理。如果我们想通过反射去修改x,我们必须传给反射库我们想修改的值的指针。
This is straightforward and familiar, and reflection works the same way. If we want to modify x by reflection, we must give the reflection library a pointer to the value we want to modify.
让我们按部就班吧。首先,我们如平常一样始初化x并创建一个反射值指向它,称之为p。
Let’s do that. First we initialize x as usual and then create a reflection value that points to it, called p.
1 | var x float64 = 3.4 |
输出则是
The output so far is
1 | type of p: *float64 |
反射对象p是不可设置的,但是我们想设置的并不是p,而是p的指针。我们调用值中的Elem方法来得到p所指向的地址,它直接通过指针,并在称为v的反射数值中保留结果:
The reflection object p isn’t settable, but it’s not p we want to set, it’s (in effect) *p. To get to what p points to, we call the Elem method of Value, which indirects through the pointer, and save the result in a reflection Value called v:
1 | v := p.Elem() |
现在v是可设置的反射对象了,作为输出论证,
Now v is a settable reflection object, as the output demonstrates,
1 | settability of v: true |
既然它代表了x,我们最终能使用v.SetFloat来修改x的值:
and since it represents x, we are finally able to use v.SetFloat to modify the value of x:
1 | v.SetFloat(7.1) |
输出,如所期望的
The output, as expected, is
1 | 7.1 |
反射不容易被理解,但是它所做的事情切实是程序语言所能做的事情,虽然通过反射的Types和Values能掩饰所发生的过程,不过只要记住反射的Values
需要它的地址,这样才可去修改它所显示出来的内容。
Reflection can be hard to understand but it’s doing exactly what the language does, albeit through reflection Types and Values that can disguise what’s going on. Just keep in mind that reflection Values need the address of something in order to modify what they represent.
结构体
Structs
在我们上一个例子中,v并不是指针本身,它只是从指针中衍生出来的。通常我们在使用反射去修改结构体中的字段时会出现这种情况。只要我们有结构体的地址,我们能修改它里面的字段。
In our previous example v wasn’t a pointer itself, it was just derived from one. A common way for this situation to arise is when using reflection to modify the fields of a structure. As long as we have the address of the structure, we can modify its fields.
这里有一个简单的例子来解析一下结构体数值t。我们创建了这个结构体地址的反射对象,因为我们想稍候去修改它。然后我们设置typeOfT为它即t的类型,并用最直接的方法调用来迭代字段(详细可见反射包)。注意我们从结构的类型中提取字段的名称,但是字段本身是规则的reflect.Value对象。
Here’s a simple example that analyzes a struct value, t. We create the reflection object with the address of the struct because we’ll want to modify it later. Then we set typeOfT to its type and iterate over the fields using straightforward method calls (see package reflect for details). Note that we extract the names of the fields from the struct type, but the fields themselves are regular reflect.Value objects.
1 | type T struct { |
这段代码输出
The output of this program is
1 | 0: A int = 23 |
关于settability这里还有好几个要介绍的点:T的字段名是大写的(可被导出)因为只有可导出的字段才可设置。
There’s one more point about settability introduced in passing here: the field names of T are upper case (exported) because only exported fields of a struct are settable.
【个人理解】
其实很好理解,把上面的
1 | type T struct { |
改成
1 | type T struct { |
直接报错:panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
因为s包含可设置的反射对象,我们能修改结构体中的字段。
Because s contains a settable reflection object, we can modify the fields of the structure.
1 | s.Field(0).SetInt(77) |
这里是结果:
And here’s the result:
1 | t is now {77 Sunset Strip} |
如果我们修改程序使s是通过t被创建的,而不是&t,然后调用SetInt和SetString将会报错,t的字段无法被设置。
If we modified the program so that s was created from t, not &t, the calls to SetInt and SetString would fail as the fields of t would not be settable.
结论
Conclusion
这里再次强调反射的定律:
Here again are the laws of reflection:
反射从接口数值到接口对象
Reflection goes from interface value to reflection object.
反射从反射对象到接口数值
Reflection goes from reflection object to interface value.
若修改反射对象,其值一定要是可设置的。
To modify a reflection object, the value must be settable.
一旦你理解了Go语言中的反射定律,Go语言将变得很容易使用,虽然它保持着一些微妙之处。这是非常强力的工具,它应该被小心使用,并且不到必要时刻仍然要避开对它的使用。
Once you understand these laws reflection in Go becomes much easier to use, although it remains subtle. It’s a powerful tool that should be used with care and avoided unless strictly necessary.
写到这里,我们其实还有大量的反射的内容未提及,channels的发送和接收,内存分配,使用slices和maps,调用方法和函数——但是这篇文章已经足够长了。我们将在以后的文章再研究这些内容。
There’s plenty more to reflection that we haven’t covered — sending and receiving on channels, allocating memory, using slices and maps, calling methods and functions — but this post is long enough. We’ll cover some of those topics in a later article.
作者 Rob Pike
By Rob Pike
后记:
反射的内容我已经都看过一遍,并且记下笔记,在这里分享一下,如果有兴趣可以看一下,内容比较多:
https://github.com/gundamzaku/golang_study_note/tree/master/%E5%8F%8D%E5%B0%84reflect









