Go语音提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内 在操作,但是在编译时并不知道这些变量的具体类型。这种机制被称为反射。反射也可以让 我们将类型本身作为第一类的值类型处理。
探讨Go语言的反射特性,看看它可以给语言增加哪些表达力,以及在两个至 关重要的API是如何用反射机制的:一个是fmt包提供的字符串格式功能,另一个是类似 encoding/json和encoding/xml提供的针对特定协议的编解码功能。对于我们在4.6节中看到过 的text/template和html/template包,它们的实现也是依赖反射技术的。然后,反射是一个复杂 的内省技术,不应该随意使用,因此,尽管上面这些包内部都是用反射技术实现的,但是它 们自己的API都没有公开反射相关的接口。
- 为何需要反射? 有时候我们需要编写一个函数能够处理一类并不满足普通公共接口的类型的值,也可能是因 为它们并没有确定的表示方式,或者是在我们设计该函数的时候还这些类型可能还不存在, 各种情况都有可能。 一个大家熟悉的例子是fmt.Fprintf函数提供的字符串格式化处理逻辑,它可以用例对任意类型 的值格式化并打印,甚至支持用户自定义的类型。让我们也来尝试实现一个类似功能的函 数。为了简单起见,我们的函数只接收一个参数,然后返回和fmt.Sprint类似的格式化后的字 符串。我们实现的函数名也叫Sprint。 我们使用了switch类型分支首先来测试输入参数是否实现了String方法,如果是的话就使用该 方法。然后继续增加类型测试分支,检查是否是每个基于string、int、bool等基础类型的动态 类型,并在每种情况下执行相应的格式化操作。
func Sprint(x interface{}) string {
type stringer interface {
String() string
}
switch x := x.(type) {
case stringer:
return x.String()
case string:
return x
case int:
return strconv.Itoa(x)
// ...similar cases for int16, uint32, and so on...
case bool: if x {
return "true"
}
return "false"
default:
// array, chan, func, map, pointer, slice, struct
return "???"
}
}
- reflect.Type和reflect.Value
反射是由 reflect 包提供支持. 它定义了两个重要的类型, Type 和 Value. 一个 Type 表示一个 Go类型. 它是一个接口, 有许多方法来区分类型和检查它们的组件, 例如一个结构体的成员或 一个函数的参数等. 唯一能反映 reflect.Type 实现的是接口的类型描述信息, 同样的实体 标识了动态类型的接口值. 函数 reflect.TypeOf 接受任意的 interface{} 类型, 并返回对应动态类型的reflect.Type:
t := reflect.TypeOf(3) // a reflect.Type
fmt.Println(t.String()) // "int"
fmt.Println(t) // "int"
其中 TypeOf(3) 调用将值 3 作为 interface{} 类型参数传入.将一个具体的值转为 接口类型会有一个隐式的接口转换操作, 它会创建一个包含两个信息的接口值: 操作数的动态 类型(这里是int)和它的动态的值
因为 reflect.TypeOf 返回的是一个动态类型的接口值, 它总是返回具体的类型. 因此, 下面的代 码将打印 "*os.File" 而不是 "io.Writer". 稍后, 我们将看到 reflect.Type 是具有识别接口类型的 表达方式功能的
var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // "*os.File"