接触过 Java 的同学对反射的概念一定不陌生。不过这里大多数同学没接触过 Java,也不用担心。
反射是一种能让程序自身去检查和操作变量的一种能力,即使不知道变量的类型也没有关系。
在 C++ 里,如果你不知道一个 class 的定义,你就没有办法去调用这个 class 的任何方法,操作这个 class 的任何成员,所以说 C++ 是一种不支持反射能力的语言。这不是说 C++ 办不到这件事情,我们强大的 C++ 可以干任何事,只是实现起来没那么容易。
好在 Golang 天然提供了 reflect 包,原生支持了反射能力。
1. 反射的应用场景
一个最典型的场景就是 encoding/json 包了,相信你还记得这个包是干啥的。在不知道具体的 struct 结构的情况下,encoding/json 就能把你的 struct 序列化成 json,是不是很神奇?
另外,你用的最多的 fmt 包,为什么也能在不知道变量类型的情况下,把数据打印出来呢?其实也是用到了反射。
除此之外,作为框架的编写者,也可能会使用 reflect 来完成一些重复的工作。
反射虽好,但不能滥用。它是一切困惑的源泉。
2. Golang 中的类型
在介绍反射前,我们需要搞清楚一些基本的概念。
- 静态类型
Golang 是静态类型语言,所谓静态,是指在编译期确定好所有变量的类型。例如:
type Second int32
var i int32
var
变量 i
的静态类型是 int32
,而 s
的静态类型是 Second
. 变量 i
和 s
是两种完全不同的类型。
- 底层类型
在上面的例子里,变量 s
的静态类型是 Second
, 底层类型是 int32
.
- 接口
接口(interface)是一种很特殊的类型,因此单独拿出来讲。有些人说 interface 是动态类型,这是误导人的说法,接口类型也是静态类型。
很久以前我们在介绍接口的时候,就说过接口值中会保存一个 pair,分别是“类型描述符”和“具体值”(参考《接口值》)。
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty
例如上面的例子,r
是一个 (value, type)
pair, 保存的内容是 (tty, *os.File)
. 无论如何,接口值中的类型描述符描述的都是“具体值”的类型。所以下面的操作是合法的:
var
一个比较特殊的接口类型是 interface{}
,前面我们都遇到过无数次了。
需要强调的是,变量 r
的静态类型是 io.Reader
. 变量 w
的静态类型是 io.Writer
.
3. Type & Value
在学习反射前,首先需要了解 reflect.Type
和 reflect.Value
类型。前者是一个 interface 类型,后者是一个 struct 类型。这两个类型都在 reflect 包中定义,如果你要使用它,务必记得 import "reflect"
.
3.1 reflect.Type
Go 中的类型,可以使用 reflect.Type 进行描述和表示。
Type is the representation of a Go type.
这意味着如果你持有一个 reflect.Type
值,你就能知道关于某个类型的所有信息,比如类型的名字,所占的 bit 数,类型的方法等。如果是个结构体,你还能获取到这个结构体的所有字段名。
3.2 reflect.Value
如果说 reflect.Type
是模具,那 reflect.Value
就是从模具里倒出来的实体。
reflect.Value
可以表示 Go 中某个具体的值。这意味着如果你持有 reflect.Value
类型变量,你就能知道关于这个变量所有信息,比如这个变量的静态类型,底层类型,值等信息。
3.3 取到 Type 和 Value
reflect.TypeOf 函数和 reflect.ValueOf 函数可以从 interface{} 类型中取到 Type 和 Value. 这两个函数的签名如下:
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Type
通过 reflect.Value
的 TypeOf
方法,你也可以拿到 Type
. 不过,我们最好还是使用 TypeOf
函数。为了学起来不那么痛苦,我们应该严格区分 Type
& Value
.
- Type
下面是取 Type 的例子:
var i int32 = 3
var f float64 = 5.6
t1 := reflect.TypeOf(i)
t2 := reflect.TypeOf(f)
// 有了 t1 和 t2 你就能知道 i 和 f 的具体类型了
fmt.Printf("typeof i = %v\n", t1.String()) // typeof i = int32
fmt.Printf("typeof f = %v\n", t2.String()) // typeof f = float64
Type 除了有 String 方法外,还有其它许多方法,后面我们会挑一些比较重要的介绍。
- Value
从 interface 也能获取到 Value 值,如:
var i int32 = 3
var f float64 = 5.6
var s string = "hello world"
v1 := reflect.ValueOf(i)
v2 := reflect.ValueOf(f)
v3 := reflect.ValueOf(s)
// Value 的 String 方法可以返回底层的 string 值,该方法只适用于具体类型为 string 的值。
// 对于其它类型,则返回 <T Value> 这样的形式的字符串。其中 T 是具体类型。
fmt.Printf("valueof i = %v\n", v1.String()) // valueof i = <int32 Value>
fmt.Printf("valueof f = %v\n", v2.String()) // valueof f = <float64 Value>
fmt.Printf("valueof s = %v\n", v3.String()) // valueof f = hello world
// 直接打印. fmt 包会对 Value 类型作特殊处理,因此 fmt 不会调用 Value 的 String 方法,而是直接打印 Value 所持有的具体值。
fmt.Printf("valueof i = %v\n", v1) // valueof i = 3
fmt.Printf("valueof f = %v\n", v2) // valueof f = 5.6
fmt.Printf("valueof s = %v\n", v3) // valueof f = hello world
4. 总结
- 知道反射能干什么
- 掌握 Type 和 Value 类型
- 掌握 TypeOf 方法和 ValueOf 方法