GO 反射建立在类型系统,Go 是静态类型语言,变量在编译时即可确定其所属的静态类型。
对底层类型相同但静态类型不同的变量,在未做类型转换时,无法相互赋值。举例:
type MyInt int
var i int = 8
var j MyInt = 8
j = i // cannot use i (type int) as type MyInt in assignment
i、j 分别对应 int 和 MyInt 静态类型,它们底层数据类型都是 int,但无法相互赋值。
接口类型,是一种重要的类型,表示一个确定的方法集。只要某个具体值(非接口)实现了某个接口中的方法,那么该接口类型的变量就可以存储这个具体值。
一个众所周知的例子就是 io.Reader 和 io.Writer,即 io 包中的 Reader 和 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)
}
任何实现了 Read(或 Write)方法及其签名的类型,也就实现了 io.Reader(或 io.Writer)接口,换句话说,某个值的类型拥有 Read 方法,io.Reader 类型的变量就能够存储这个值:
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// 等等
以上,r 无论存储何值,r 的类型总是 io.Reader,即 r 的静态类型为 io.Reader
由于任何值都有 0 至多个方法,因此 空接口 能存储 任何值。
接口类型的变量存储了一对内容:
例如,在执行完
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 还实现了除 Read 以外的其它方法:尽管该接口值只提供了访问 Read 方法的能力,但其内部却携带了有关该值的所有类型信息。 这就是我们可以写出这种代码的原因:
var w io.Writer
w = r.(io.Writer)
通过类型断言:断言 r 内的条目是否实现了 io.Writer,因此我们可以将它赋予 w 。赋值后,w 将会包含一对 ( tty , *os.File )。这与保存在 r 中的一致。 接口的静态类型决定了哪些方法可通过接口变量调用, 即便其内部的具体值可能有更大的方法集。
接着,我们可以这样做:
var empty interface{}
empty = w
空接口值 e 也将再次包含同样的一对 ( tty, *os.File )。 这很方便:空接口可保存任何值,同时包含关于该值的所有信息。
(在这里我们无需类型断言,因为 w 肯定是满足空接口的。在本例中, 我们将一个值从 Reader 变成了 Writer ,由于 Writer 的方法集并非 Reader 方法集的子集,因此我们必须显式地使用类型断言。)
一个很重要的细节,就是接口内部的对总是 (值, 具体类型) 的形式,而不会是 (值, 接口类型) 的形式。接口不能保存接口值。
反射只是一种检查存储在接口变量中的 (类型-值) 对的机制。
通过,reflect 包下述两个类型,可以访问接口值的类型、值的具体内容:
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))// type: float64
此处 x 为 float64 类型,并非 接口值,是如何从接口值反射出 x 类型信息的?这是由于包 reflect.TypeOf 函数签名包含空接口
// TypeOf 返回 interface{} 中的值的反射类型 Type。
func TypeOf(i interface{}) Type
在传入 x 时,x 作为实参存储到形参的空接口,reflect.TypeOf 通过解包该空接口还原其类型信息。同理 reflect.ValueOf 也能还原 x 的值:
var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x).String())// value: <float64 Value>
调用 String() 方法的原因是 fmt 包默认会调用 reflect.ValueOf 还原其具体值。
通过 reflect.Interface 方法可还原 reflect.Value 类型的接口值。
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
p := reflect.ValueOf(x)
fmt.Println("p:", p)// p: 3.4
fmt.Println("p[interface]:", p.Interface())//p[interface]: 3.4
}
虽然打印结果上看:fmt.Println("p:", p) 和 fmt.Println("p[interface]:", p.Interface()) 都是打印出 3.4。
但是 p 是 Value 类型数据,会被 fmt 进行内部解包,输出值 3.4,而 p.Interface() 则是获取 p (Value) 的接口值。
fmt.Println("p'type:", reflect.TypeOf(p)) // p'type: reflect.Value
fmt.Println("p[interface]'type:", reflect.TypeOf(p.Interface())) // p[interface]'type: float64
以上,简单来说 Interface 方法是 ValueOf 的你操作。
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // panic: reflect: reflect.Value.SetFloat using unaddressable value
fmt.Println("settability of v:", v.CanSet())// settability of v: false
可设置性 是反射值 Value 的一种属性,并非所有的反射值都拥有它,可以通过 CanSet 方法检测反射值是否有可设置性。
上述实例中通过 v.SetFloat 报错的原因是:v 是 x 的反射对象 Value 而非 x 本身;另外,x 作为实参传入 ValueOf 会出会创建空接口参数作为 x 的副本,因而即使能够设置新值,也不会更新 x 值本身。
如何使反射值 v 可设置呢?再来看看下面的示例:
var x float64 = 3.4
p := reflect.ValueOf(&x) // 注意:获取 x 的地址
fmt.Println("type of p:", p.Type())// type of p: *float64
fmt.Println("settability of p:", p.CanSet())//settability of p: false
v := p.Elem()
fmt.Println("settability of v:", v.CanSet())// settability of v: true
v.SetFloat(7.1)
fmt.Println(v.Interface())// 7.1
fmt.Println(x)// 7.1