Golang的runtime包中类型元数据以及空接口和非空接口结构类型都是未导出的,所以reflect包中又定义了一套,这些类型定义在两个包中保持一致。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//runtime
type _type struct
type uncommonType struct
type interfacetype struct
type slicetype struct
...
// src/reflect/type.go
type rtype struct
type uncommonType struct
type interfaceType struct
type sliceType struct
...
reflect.Type
reflect 包提供 TypeOf 函数,用于获取一个变量的类型信息,它接收一个空接口类型的参数,并返回一个 reflect.Type 类型的返回值。
1
2
3
4
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
reflect.Type 是一个非空接口,提供了一系列方法可获取类型各方面的信息,例如对齐边界、方法、类型名称、包路径、是否实现指定接口、是否可比较…等等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Type interface {
// 对齐边界
Align() int
FieldAlign() int
//方法
Method(int) Method
MethodByName(string) (Method, bool)
NumMethod() int
PkgPath() string //包路径
Implements(u Type) bool //是否实现指定接口
Comparable() bool //是否可比较
//获取指针指向的元素类型
Elem() Type
...
}
看下TypeOf函数使用的例子:
1
2
3
4
5
6
7
8
9
10
11
12
type Person struct {
Name string
}
func (p Language) ShowName() {
fmt.Println(p.Name)
}
func main() {
a := Person{Name: "hello"}
t := reflect.TypeOf(a)
fmt.Println(t.Name(), t.NumMethod())
}
Go语言中传参都是值拷贝,reflect.TypeOf的参数是一个空接口类型,那么传参参数需要一个引用地址,这里传入的参数是a的 reflect.TypeOf在编译阶段会增加一个临时变量作为a的拷贝,然后再使用临时变量的地址。
其实所有参数为空接口类型的情况都要像这样,通过传递拷贝变量后的地址来实现传值的语义。
接下来TypeOf函数会将runtime.eface类型的参数转换为reflect.emptyInterface类型,并赋值给变量eface,这两个类型的结构是一致的,转换以后方便reflect包操作内部元素,因为*rtype类型实现了Type接口。所以接下来要做的就是把eface.typ包装称reflect.Type类型的返回值,TypeOf的任务就结束了。
reflect.Value
再来看看通过反射修改变量值是怎么回事,这就需要通过使用reflect.Value类型了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/reflect/value.go
type Value struct {
typ *rtype //存储反射变量的类型元数据指针
ptr unsafe.Pointer //存储数据地址
flag //位标识符,存储反射值的一些描述信息,例如是否为指针、是否为方法、是否只读等等
}
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
escapes(i)
return unpackEface(i)
}
通常会使用reflect.ValueOf来拿到一个reflect.Value,注意reflect.ValueOf的参数也是一个空接口类型,所以和TypeOf参数处理方式一样。
除此之外,ValueOf函数会通过escapes显式的把参数指向的变量逃逸到堆上。
例子:
1
2
3
4
5
6
func main() {
a := "hello"
v := reflect.ValueOf(a)
v.SetString("newhello")
println(a)
}
这里项通过反射修改一个string类型的变量a的值。编译阶段会增加一个临时变量作为a的拷贝,同TypeOf不一样的是这个临时变量会被显式的逃逸到堆上,栈上只留它的地址。
接下来通过v调用SetString时,因为指向的是a的拷贝而不是a,而修改一个用户都不知道的临时变量没有任何意义,所以会发生panic:
1
panic: reflect: reflect.Value.SetString using unaddressable value
这样反射修改变量值是行不通的,若要修改成功就要反射a的指针,这样ValueOf函数参数指向的变量就是a的指针。
1
2
3
4
5
6
7
func main() {
a := "hello"
v := reflect.ValueOf(&a)
v := v.Elem()
v.SetString("newhello")
println(a)
}
通过 Elem() 方法获取这个指针指向的元素类型。这个获取过程称为取元素,等效于对指针类型变量做了一个*操作。