Scu_laji

GO语言中的反射机制

GO 语言中的反射机制

写在前头,我对 Java 这种纯粹的面向对象的语言不是很熟悉,于是对反射也不是很熟。

首先,让我们来了解一下 go 的类型系统

go 语言是一个严格的静态类型的语言。

如果我们做出如下定义

1
2
3
4
type MyInt int
var i int
var j MyInt

那么 i 就是类型 int, j 是类型 MyInt。

尽管这两个变量拥有相同的基础 type, 它们也不能被显式的赋值。

另一个需要提到的类型是 interface,这个类型比较特殊,因为一个 interface 可以储存任意实现了这个 interface 的 type 的变量(当然,这个变量不能是一个 interface)

大家最熟悉的 🌰 就是 io.Reader and io.Write

1
2
3
4
5
6
7
8
9
// 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)
}
1
2
3
4
5
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on

那么 r 到底是一个什么类型呢?
r 的类型始终是 io.Reader

另一种极端的状况是,任何 type 都实现了一个空的 interface.

那么就意味着这个空的 interface 可以存储任何的 value.

这里有个误解就是,有人说 go 的 interface 是一个动态类型,其实不然,一个 interface 变量始终拥有相同的 type,即使在运行期 interface 中存储的值发生了改变,那么这个新的值也需要满足这个 interface.

interface 中包含哪些东西

Russ Cox 写了一篇 Go Data Structures: Interfaces , 深入解释了 GO 语言中的 interface 类型,在此我仅做一个简短总结

一个 interface type 的变量,应该存储一对信息,(变量的值,变量类型),更精确的说,这个变量的值所依赖的基础 type 实现了这个 interface, 这个 type 描述了这个变量的所有细节.

1
2
3
4
5
6
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty

依旧是举个 🌰, 这里的 r 存储了一对信息 (tty, os.File) , 注意这里的os.File 类型实现了其他的方法,例如(Write)。

即使 r 只提供对 Reader 的访问,但 r 内仍存储了其存储的值的所有 type 信息。

这也就是为什么我们仍然可以做如下的事情的原因

1
2
var w io.Writer
w = r.(io.Writer)

r.(io.Writer)是一种 Comma-ok 断言式写法,可以去搜索一下,这里就不展开了。

让我们更进一步

1
2
var empty interface{}
empty = w

那么 empty 就也获得了相同的信息 (tty, *os.File)

以上就是 GO 关于反射的基础知识。

反射

反射从接口值中获取对象

我们需要了解两种类型,reflect 包中的 Type,Value 类型,这两种类型使我们能够用访问 interface 变量中的内容,以及
两个简单的 function, reflect.TypeOf and reflect.ValueOf, 返回值分别是上述的两种类型。

1
2
3
4
5
6
7
8
9
10
11
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
}

这个程序的输出很容易想到,但是,你也许会有疑问就是这个 x,它并不是一个 interface 类型的变量。

标准文档该诉我们, TypeOf 接受一个 interface{} 类型的参数,而我们知道,任何一个 type 都实现了 interface{} 接口。

当我们调用 reflct.TypeOf()时,x 首先被存储到一个 interface{}中,然后 reflect.TypeOf 从这个空 interface 中获取 type 信息。

同理, ValueOf 的原理也是一样的。

ValueOf 返回的 Value 实现了 Type 方法,Kind 方法。

反射库有两个特性值得单独提出来讲一讲,首先,为了保证 API 的简洁性, reflect 库的 getters 和 setters 方法操作
的是最大的能够存储该值的类型,也就是说,int64 是所有有符号整形数的 getters(setters)方法的返回值(参数)类型。

1
2
3
4
5
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 方法返回的是基础类型,也就是说

1
2
3
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)

v.Kind() 将会返回 relect.Int, 即使 x 的静态类型是 MyInt.

从反射对象中获取 interface

给定一个 reflect.Value,我们可以从中得到一个 interface 值。

1
2
3
4
5
var x uint8 = 'x'
v := reflect.ValueOf(x)
fmt.Println("v:",v,"test",v.Interface())
// It's output
// v: 120 test 120

那为什么要用这个 Interface 呢?我直接用 v 也能获取到值啊
, 好像是这个样子的,我也不是很清楚为什么要这样用。 若有见解,欢迎联系,讨论。

改变一个反射对象,值必须是可设定的

1
2
3
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // panic: reflect: reflect.Value.SetFloat using unaddressable value

引起这个 panic 的问题不在于 7.1 不是可寻址的,而在于 v 是不可设定的,可设定性是一个 reflection value 的属性,并且并不是所有的 reflection Value 都具有这个属性。

幸运的是,有一个 CanSet 方法提供了一个 reflection Value 是否具有可设定性。

1
2
3
4
5
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())
// output
// settability of v: false

但是什么是可设定性?

可设定性有点像可寻址性,但是更严格一点。

可设定性反映了一个 reflection object 能修改一个确定的存储区。这个存储区被这个 reflection object 使用。

与 c 语言中传值与传址类似,要在函数内改变一个变量的值,只能使用传址的方式。

1
2
3
4
5
6
7
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())
// output
//type of p: *float64
//settability of p: false

下面,我们要修改 reflection value 的值的话

1
2
3
4
5
v := p.Elem()
fmt.Println("settability of v:", v.CanSet())
v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)

以上隐式的修改一个变量的值,减少了程序猿因粗心犯得错误。

结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)

这里定义的 typeOfT 主要是为了方便遍历。

That’s all. 本人水平有限,如有疏漏,望不吝赐教。