文章目录
- 反射使用常见场景
- 反射原理
- 反射的基本函数
反射使用常见场景
需要反射的 2 个常见场景:
- 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
- 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。
在讲反射的原理以及如何用之前,还是说几点不使用反射的理由:
(1)与反射相关的代码,经常是难以阅读的。在软件工程中,代码可读性也是一个非常重要的指标。
(2)Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。
(3)反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。
反射原理
interface是 Go 语言实现抽象的一个非常强大的工具。当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息,反射就是通过接口的类型信息实现的,反射建立在类型的基础上。
go语言的两个特点:
- go语言是静态类型语言,因此在程序编译阶段,类型已经确定。
- interface{}空接口可以和任意类型进行交互,因此可以利用这一特点实现对任意类型的反射。
对于第一点,举个例子:
Go 语言中,每个变量都有一个静态类型,在编译阶段就确定了的,比如 int, float64, []int 等等。注意,这个类型是声明时候的类型,不是底层数据类型。
要理解这一点,需要明白下面这个代码:
type MyInt int
var i int
var j MyInt
我们知道,尽管 i,j 的底层类型都是 int,但我们知道,他们是不同的静态类型,除非进行类型转换,否则,i 和 j 不能同时出现在等号两侧。j 的静态类型就是 MyInt。这就是声明时候的类型,而不是底层数据类型。
对于第二点,反射主要与 interface{} 类型相关。
在go语言的实现中,每一个interface变量简单的来看都有一个pair,pair中记录了实际变量的值和类型:(value, type)
,反射就是用来检测存储在接口变量内部(值value;类型concrete type) pair对的一种机制。
对于Go语言中的Reader 和 Writer 接口:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
对于下面的代码:
func main() {
var r io.Reader
fmt.Printf("type: %T \n", r)
tty, err := os.OpenFile("solution.go", os.O_RDWR, 0)
if err != nil {
fmt.Printf("err: %s\n", err)
}
r = tty
fmt.Printf("type: %T \n", r)
}
其输出为:
type: <nil>
type: *os.File
所以,首先声明 r 的类型是 io.Reader,注意,首先这里的io.Reader的类型是interface,具体来说是iface而不是eface。这时, r 的静态类型就是io.Reader这个iface,但此时对于这个iface,它的动态类型为 nil,并且它的动态值也是 nil。
之后,r = tty 这一语句,将 r 的动态类型变成 *os.File
,动态值则变成非空,表示打开的文件对象。这时,r 可以用<value, type>
对来表示为:<tty, *os.File>
这个赋值的操作,实际上完成了如下图所示的操作:
此时虽然 fun 所指向的函数只有一个 Read 函数,其实 *os.File
还包含 Write 函数,也就是说 *os.File
其实还实现了 io.Writer
接口所具备的方法,go实现接口都是隐式实现。所以,下面的断言语句可以执行:
func main() {
var r io.Reader
fmt.Printf("type: %T \n", r)
tty, err := os.OpenFile("solution.go", os.O_RDWR, 0)
if err != nil {
fmt.Printf("err: %s\n", err)
}
r = tty
fmt.Printf("type: %T \n", r)
w, ok := r.(io.Writer) // 断言
if !ok {
fmt.Printf("fail")
}
}
最后再来个代码:
func main() {
var r io.Reader
fmt.Printf("type: %T \n", r)
tty, err := os.OpenFile("solution.go", os.O_RDWR, 0)
if err != nil {
fmt.Printf("err: %s\n", err)
}
r = tty
fmt.Printf("type: %T \n", r)
var w io.Writer
w, ok := r.(io.Writer)
if !ok {
fmt.Printf("fail")
}
fmt.Printf("type: %T\n", w)
var empty interface{}
empty = w
fmt.Printf("type: %T \n", empty)
}
由于 empty 是一个空接口,因此所有的类型都实现了它,w 可以直接赋给它,不需要执行断言操作。
反射的基本函数
reflect 包里定义了一个接口 reflect.Type
和一个结构体 reflect.Value
,它们提供很多函数来获取存储在接口里的类型信息。
reflect 包中提供了两个基础的关于反射的函数来获取上述的接口和结构体:
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
直接来看源码:
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
func toType(t *rtype) Type {
if t == nil {
return nil
}
return t
}
这个emptyInterface
与eface
实际上是一样的,只是字段名有些差别,在不同的源码包下。至于toType会直接返回这个eface的Type
。
返回值 Type
实际上是一个接口,定义了很多方法,用来获取类型相关的各种信息,如果直接输出:
...
r = tty
fmt.Printf("type: %T \n", r)
fmt.Println(reflect.TypeOf(r))
实际上在print的时候,会自动调用Type
接口的String()方法,所以会打印出来*os.File
。
再来看一下 ValueOf 函数:
// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
// TODO: Maybe allow contents of a Value to live on the stack.
// For now we make the contents always escape to the heap. It
// makes life easier in a few places (see chanrecv/mapassign
// comment below).
escapes(i)
return unpackEface(i)
}
escapes(i)
暂时可以忽略这个方法,主要关注unpackEface(i)
。
// unpackEface converts the empty interface i to a Value.
func unpackEface(i interface{}) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
// NOTE: don't read e.word until we know whether it is really a pointer or not.
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
}
将先将 i 转换成*emptyInterface
类型, 再将它的 typ 字段和 word 字段以及一个标志位字段组装成一个 Value 结构体,而这就是 ValueOf 函数的返回值,它包含类型结构体指针、真实数据的地址、标志位。