文章目录

  • 反射使用常见场景
  • 反射原理
  • 反射的基本函数


反射使用常见场景

需要反射的 2 个常见场景:

  • 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
  • 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。

在讲反射的原理以及如何用之前,还是说几点不使用反射的理由:
(1)与反射相关的代码,经常是难以阅读的。在软件工程中,代码可读性也是一个非常重要的指标。
(2)Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。
(3)反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。

反射原理

interface是 Go 语言实现抽象的一个非常强大的工具。当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息,反射就是通过接口的类型信息实现的,反射建立在类型的基础上。

go语言的两个特点:

  1. go语言是静态类型语言,因此在程序编译阶段,类型已经确定
  2. 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>

这个赋值的操作,实际上完成了如下图所示的操作:

go语言处理get和post方法 go语言reflect_go语言处理get和post方法


此时虽然 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")
	}
}

go语言处理get和post方法 go语言reflect_go语言处理get和post方法_02

最后再来个代码:

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 可以直接赋给它,不需要执行断言操作。

go语言处理get和post方法 go语言reflect_静态类_03

反射的基本函数

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
}

这个emptyInterfaceeface实际上是一样的,只是字段名有些差别,在不同的源码包下。至于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 函数的返回值,它包含类型结构体指针、真实数据的地址、标志位。

go语言处理get和post方法 go语言reflect_静态类_04