理解
反射是建立在类型系统之上的,所以要理解反射必须先理解 golang 中的类型。
Go是静态类型的。每个变量都有一个静态类型,也就是说,只有一种类型是已知的,在编译时是固定的:int、float32、*MyType、[]byte,等等。如果我们声明:
type MyInt int
var i int
var j MyInt
i 是 int 类型,j 是 MyInt 类型。变量 i 和 j 具有不同的静态类型,尽管它们具有相同的底层类型,但在不进行转换的情况下,它们不能相互赋值。
类型的一个重要类别是接口类型,它表示固定的方法集。接口变量可以存储任何具体的(非接口的)值,只要该值实现了接口的方法。一对众所周知的例子是 io.Reader 和 io.Writer
// 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)
}
任何使用这个签名实现读(或写)方法的类型都被称为实现 io.Reader 或 io.Writer。所以 io.Reader 类型的变量可以赋值给所有实现了 Reader 接口的值。
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on
无论 r 可能包含什么具体值,r 的类型始终是 io.Reader:Go是静态类型的,而 r 的静态类型是 io.Reader。
接口类型的一个非常重要的例子是空接口:
interface{}
它表示空的方法集,任何值都实现了它,因为任何值都有零个或多个方法。
有些人说Go的接口是动态类型的,但这是误导。它们是静态类型的:接口类型的变量总是具有相同的静态类型,即使在运行时存储在接口变量中的值可能改变类型,该值也总是满足接口的要求。
反射和接口是密切相关的,要理解反射就要先理解接口。
接口的表示
一个接口类型的变量包含:赋给变量的具体值和该值的类型描述符。值是实现接口的基础具体数据项,而类型描述该项的完整类型。例如:
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty
这里 r 的类型 type 是 *os.File, 值 value 是 ttl。*os.File 实现了 Read 以外的其他方法。尽管接口值只提供对Read方法的访问,但里面的值携带关于该值的所有类型信息。所以我们还可以进行下面的操作:
var w io.Writer
w = r.(io.Writer)
r.(io.Writer) 是类型断言表达式;它断言的是 r 里面的项也实现了 io.Writer。赋值后,w 将包含和 r 相同的 type 和 value。接口的静态类型决定了可以用接口变量调用哪些方法,即使其中的具体值可能有更大的方法集。
我们还可以这样:
var empty interface{}
empty = w
这里的空接口变量 empty 包含和 r 相同的 type 和 value。空接口可以保存任何值,并包含我们可能需要的关于该值的所有信息。
反射三定律
反射第一定律
1.反射将接口变量转换为反射对象 Type 和 Value。
从本质上讲,反射只是一种检查存储在接口变量中的类型和值对的机制。
根据上面所说,任何接口由两部分组成:接口的具体类型、具体类型对应的值。例如 var a int = 3,变量 a 可以转换为一个空接口类型 interface{},此时这个变量在 go 反射中的表示就是<Value,Type>,其中 Value是变量 a 的值,Type 是变量 a 的类型 int。
在 go 反射中,标准库为我们提供两种类型: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
TypeOf 函数签名是一个空接口 interface{}。
// TypeOf returns the reflection Type of the value in the interface{}.
func TypeOf(i interface{}) Type
当我们调用 reflect.TypeOf(x)时,x 首先保存在空接口中,然后作为参数传递。reflect.TypeOf 再从空接口中获取 x 的类型信息。
reflect.ValueOf 可以获取 x 的值信息:
var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x))
输出:
value: 3.4
reflect.Type 和 reflect.Value 包含许多方法:
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type()) // Type 返回 reflect.ValueOf 的类型
fmt.Println("kind is float64:", v.Kind() == reflect.Float64) // Kind 返回内置的元类型
fmt.Println("value:", v.Float())
输出:
type: float64
kind is float64: true
value: 3.4
反射库为了保持 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)
fmt.Println(v.Kind())
输出:
int
这里 MyInt 是用户自定义的类型,但是 Kind 返回的是 x 的底层类型 int,而不是静态类型 MyInt。
反射第二定律
2.反射可以通过反射对象 Value 还原成原先的接口变量
这个指的就是 Value 可以通过 Interface() 方法还原回接口变量,如果要转换成原先的变量还需要经过一次断言。
// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}
例子:
y := v.Interface().(float64) // 转换为接口变量,然后断言转换为原来的类型
fmt.Println(y)
这里打印结果是:变量 y float64 的值。
反射第三定律
3.要修改一个反射对象,前提是该值是可以被修改的
先来看看如下代码:
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.
运行这段代码会导致 panic :
panic: reflect.Value.SetFloat using unaddressable value
因为 v 这里是不可设置的。可设置性是 reflect.Value 的属性,并非所有 reflect.Value 都具有它。
我们可以通过 CanSet 方法来判断该值能否被设置:
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())
输出:
settability of v: false
对不可设置的 Value 调用 Set 方法是错误的。但是怎么理解可设置性了?
可设置性有一点像可寻址性,但是更严格。
var x float64 = 3.4
v := reflect.ValueOf(x)
这里我们实际传递的是 x 的副本给 reflect.ValueOf。所以,作为 reflect.ValueOf 的参数创建的接口值是 x 的副本,而不是 x 本身。
v.SetFloat(7.1)
如果上面的 SetFloat 能执行成功。虽然 x 看起来像是从 v 创建而来,也不会更新 x 的值。相反,它会更新存储在反射 reflect.Value 中 x 的副本,而 x 本身不会收到影响。这样做没有任何作用,所以是非法的,可设置性就是为了避免这个问题的。
如果我们想通过反射来修改 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
这里反射对象 p 是不可被设置的,我们真正要设置的不是 p ,而是 *p(指针指向的值)。我们可以通过 Elem 方法获取到这个值。
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
结构体 struct
下面是一个反射 struct 的例子。这里注意,结构体字段必须是大写的(可导出)的才能被设置值。
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())
}
s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)
输出:
0: A int = 23
1: B string = skidoo
t is now {77 Sunset Strip}
参考: