基本概念

Go的反射基础是接口和类型系统。Go的反射借助了实例到接口的转换所使用的的数据结构,首先将实例传递给内部的空接口,实际上是将一个实例类型转换为接口可以表述的数据结构eface ,反射基于这个转换后的数据结构来访问和操作实例的值和类型。

在Golang的实现中,每个interface变量都有一个对应的pair,pair中记录了实际变量的值和类型:

	(value, type)

value是实际变量值,type是实际变量的类型。一个interface{}类型的变量包含两个指针,一个指针指向值的类型(对应concrete type),另外一个指针指向实际的值(对应value)。

interface及其pair的存在,是Golang中实现反射的前提,理解pair,就更容易理解反射。反射就是用来检测存储在接口变量内部(值value, 类型concrete type)pair对的一种机制。

反射规则

实例、value、type三者之间的转换关系如图所示
在这里插入图片描述

反射API

func ValueOf(i interface{}) Value 
var x int = 100
v := reflect.ValueOf(x)
func TypeOf(i interface{}) Type
var x int = 100
v := reflect.TypeOf()
// New返回的是一个Value,该Value的type为PtrTo(typ),即Value的Type是指定的typ的指针类型
func New(typ Type) Value

// Zero 返回的是一个 typ 类型的零值,注意返回的 Value 不能寻址,值不可改变
func Zero(typ Type) Value

// 如果知道一个类型值的底层存放地址,则还有一个函数可以根据type和该地址值恢复出Value的
func NewAt(typ Type, p unsafe.pointer) Value
func (v Value) Type() Type
// 该方法最通用,用来将 Value 转换为空接口,该空接口内部存放具体类型实例
// 可以使用接口类型查询去还原为具体的类型
func (v Value) Interface() (i interface{})

//Value 自身也提供丰富的方法,直接将 Value 转换为简单的类型实例, 如果类型不匹配,则直接引起panic
func (v Value) Bool() bool
func (v Value) Float() float64
func (v Value) Int() int64
func (v Value) Uint() uint64
var x int = 123
v := reflect.ValueOf(x)
fmt.Println(x.Int())   // 123  值得注意的是类型首字母要大写,如 x.Int
// 如果 v类型是接口,则 Elem()返回接口绑定的实例的Value
// 如果 v类型是指针,则返回指针的 Value,否则引起 painc
func (v Value) Elem() Value

// 如果 v 是指针,则返回指针值的 Value,否则返回 v 自身,该函数不会引起 panic
func Indirect(v Value) Value
// t 必须是Array、Chan、Map、Ptr、Slice,否则会引起panic
// Elem返回的是其内部元素的Type
t.Elem() Type
// PtrTo 返回的是指向t 的指针型Type
func PtrTo(t Type) Type
// 通过CanSet判断是否能修改
func (v Value) CanSet() bool
// 通过Set进行修改
func (v Value) Set(x Value)
var x = 10
v := reflect.ValueOf(&x)
v.Elem().SetInt(20)  //v.Elem().Kind == int
fmt.Println(x)  // 20

反射三定律

  1. 反射可以将“接口类型变量”转换为“反射类型对象”
  2. 反射可以将“反射类型对象”转换为“接口类型变量”
  3. 如果要修改“反射类型对象”,其值必须是“可写的”(settable)
反射优缺点

优点

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

缺点

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