golang 中反射 【反射第一定律】
- 反射是从接口值到反射对象 【Reflection goes from interface value to reflection object.】
简单来说,反射只是一种检查存储在接口变量中的类型和值对的机制。
首先,我们需要了解包reflect中的两种类型:类型(value)和值(Type)。这两个类型给予了对接口变量内容的访问,两个简单的函数,称为reflect.TypeOf和reflect.ValueOf,检索接口值中的reflect.Type和reflect.Value片段。(而且,从reflect.Value很容易到达对应的reflect.Type,但是现在让我们把Value和Type的概念分开。)
让我们从TypeOf开始:
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
}
此程序打印
type: float64
您可能会奇怪接口在哪里,因为程序看起来像是在向reflect. TypeOf传递float 64变量x,而不是接口值。但它就在那里;正如godoc报告的那样,reflect.TypeOf的签名包含一个空接口:
// TypeOf returns the reflection Type of the value in the interface{}.
func TypeOf(i interface{}) Type
当调用reflect.TypeOf(x)时,x首先存储在一个空接口,这个空接口会作为后续处理的参数;reflect.TypeOf解包该空接口以恢复类型信息。
当然,reflect.ValueOf函数会恢复值:
var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x).String())
打印结果:
value: <float64 Value>
(在此,我们显式调用String方法,因为默认情况下,fmt包会深入到reflect.value中以显示其中的具体值。String方法则不会。)
reflect.type和reflect.value都有很多方法让我们检查和操作接口。一个重点是:Value有一个Type方法,返回reflect.Value的Type值。另一重点是Type和Value都有一个Kind方法,该方法返回一个常量,指示存储了哪种类型的项:Uint、Float64、Slice等等。Value上的Int和Float等方法也让我们获取存储在内部的值(如int64和float64):
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
还有像SetInt和SetFloat这样的方法,但是要使用它们,我们需要理解可设置性,这是反射第三定律的主题,下面将讨论。
反射库有几个属性需要特别特别指出。首先,为了保持API的简单性,Value的“getter”和“setter”方法对可以保存值的最大类型进行操作:例如,所有有符号整数都是int64。也就是说,Value的Int方法返回一个int64,而SetInt值采用一个int64;可能需要转换为所涉及的实际类型:
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.
第二个属性是反射对象的Kind描述基础类型,而不是静态类型。如果反射对象包含用户定义的整数类型的值,如
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)
上面的代码中,v的Kind仍然是reflect.Int,即使x的静态类型是MyInt,而不是int。换句话说,Kind不能区分int和MyInt,即使Type可以。
反射第二定律
- 反射从反射对象到接口值。 【Reflection goes from reflection object to interface value.】
就像物理反射一样,Go语言中的反射也会产生自逆现象。
给定一个reflect.Value,我们可以使用Interface方法恢复一个接口值;实际上,该方法将类型和值信息打包回接口表示,并返回结果:
// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}
打印由反射对象v表示的float64值:
y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)
打印可以更简洁,因为fmt.Println、fmt.Printf等的参数都作为空接口值传递,然后由fmt包在内部解包,就像我们在前面的示例中所做的那样。因此,正确打印reflect.Value的内容所需要做的就是将Interface方法的结果传递给格式化打印例程:
fmt.Println(v.Interface())
从本文是第一次编写后,对fmt包进行了更改,以便它自动解包reflect.value,就像下面这样:
fmt.Println(v)
但为了清楚起见,在这里保留.Interface()调用。
因为我们的值是float64,如果需要,我们甚至可以使用浮点格式:
fmt.Printf("value is %7.1e\n", v.Interface())
打印结果:
3.4e+00
因此,不需要类型断言v.Interface()的结果到float64;空的接口值里面有具体值的类型信息,Printf将直接恢复它。
简而言之,Interface方法是ValueOf函数的逆函数,只是它的结果总是静态类型interface{}。
重申:反射从接口值到反射对象,然后再返回。
反射第三定律
- 若要修改反射对象,该值必须是可设置的
第三定律是最微妙和令人困惑的,但如果我们从第一原理开始,它就很容易理解。
下面是一些不工作的代码,但值得研究:
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.
如果您运行此代码,它将出现异常,并显示隐藏的消息:
panic: reflect.Value.SetFloat using unaddressable value
问题在于v是不可设置的,可设置性是反射值的一个属性,并非所有反射值都具有该属性。
Value的CanSet方法报告Value的可设置性;在我们例子中:
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())
打印:settability of v: false
由于它表示x,我们最终可以使用v.SetFloat来修改x的值:
v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)
如预期的那样,打印为:
7.1
7.1
反射可能很难理解,但它确实在做语言所做的事情,尽管通过反射类型和值可以探视正在发生的事情。请记住,如果要修改反射值所代表的内容,需要他们是饮用类型。
Structs
在我们前面的例子中,v本身并不是一个指针,它只是从一个指针派生出来的。出现这种情况的常见方式是使用反射修改结构的字段。只要我们有结构的地址,我们就可以修改它的字段。
下面是一个简单的例子,它分析了一个结构值t。我们用结构体的地址创建反射对象,因为我们以后会想修改它。然后,我们将typeOfT设置为它的类型,并使用直接的方法调用迭代字段(有关详细信息,请参阅包反射)。请注意,我们从结构类型中提取字段的名称,但字段本身是常规的reflect.value对象。
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
这里顺便介绍了关于可设置性的另一点:T的字段名是大写的(导出),因为只有结构的导出字段是可设置的。
因为s包含一个可设置的反射对象,我们可以修改结构的字段。
s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)
结果是: t is now {77 Sunset Strip}
结论:
- 反射从接口到反射对象
- 反射从反射对象到接口
- 修改反射对象,它必须是可设置的
网友评论