什么是反射?
Go语言提供了一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。
反射是指在程序运行期对程序本身进行访问和修改的能力,程序在编译时变量被转换为内存地址,变量名不会被编译器写入到可执行部分,在运行程序时程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
Go程序在运行期使用reflect包访问程序的反射信息。
C/C++ 语言没有支持反射功能,只能通过 typeid 提供非常弱化的程序运行时类型信息。
Java、C# 等语言都支持完整的反射功能。
现在的一些流行设计思想需要建立在反射基础上,如
- 控制反转(Inversion Of Control,IOC)
- 依赖注入(Dependency Injection,DI)
反射是把双刃剑,功能强大但代码可读性并不理想。若非必要,并不推荐使用反射。
Golang反射
Go语言程序的反射系统无法获取到一个可执行文件空间中或者是一个包中的所有类型信息,需要配合使用标准库中对应的词法、语法解析器和抽象语法树(AST)对源码进行扫描后获得这些信息。
Golang 中的反射主要有两个作用,即,获取类型信息 和 获取值类型。
反射的类型(Type)与种类(Kind)
在使用反射时,需要首先理解类型(Type)和种类(Kind)的区别。编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。例如需要统一判断类型中的指针时,使用种类(Kind)信息就较为方便。
反射种类(Kind)的定义
Go语言程序中的类型(Type)指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型。
种类(Kind)指的是对象归属的品种,在 reflect 包中有如下定义:
type Kind uint
const (
Invalid Kind = iota // 非法类型
Bool // 布尔型
Int // 有符号整型
Int8 // 有符号8位整型
Int16 // 有符号16位整型
Int32 // 有符号32位整型
Int64 // 有符号64位整型
Uint // 无符号整型
Uint8 // 无符号8位整型
Uint16 // 无符号16位整型
Uint32 // 无符号32位整型
Uint64 // 无符号64位整型
Uintptr // 指针
Float32 // 单精度浮点数
Float64 // 双精度浮点数
Complex64 // 64位复数类型
Complex128 // 128位复数类型
Array // 数组
Chan // 通道
Func // 函数
Interface // 接口
Map // 映射
Ptr // 指针
Slice // 切片
String // 字符串
Struct // 结构体
UnsafePointer // 底层指针
)
Map、Slice、Chan 属于引用类型,使用起来类似于指针。但是在种类常量定义中仍然属于独立的种类,不属于 Ptr。
type A struct{} 定义的结构体属于 Struct 种类,*A
属于 Ptr。
从类型对象中获取类型名称和种类
Go语言中的
类型名称对应的反射获取方法是 reflect.Type 中的 Name() 方法,返回表示类型名称的字符串;
类型归属的种类(Kind)使用的是 reflect.Type 中的 Kind() 方法,返回 reflect.Kind 类型的常量。
package main
import (
"fmt"
"reflect"
)
// 定义一个Enum类型
type Enum int
const (
Zero Enum = 0
)
func main() {
// 声明一个空结构体
type cat struct {}
// 获取结构体实例的反射类型对象」
typeOfCat := reflect.TypeOf(cat{})
// 显示反射类型对象的名称和种类
fmt.Println("Name:", typeOfCat.Name(), ", kind:", typeOfCat.Kind())
// 获取Zero常量的反射类型对象
typeOfA := reflect.TypeOf(Zero)
// 显示反射类型对象的名称和种类
fmt.Println("Name:", typeOfA.Name(), ", kind:", typeOfA.Kind())
/*
Name: cat , kind: struct
Name: Enum , kind: int
*/
}
reflect
Go语言中的反射是由 reflect 包提供支持的
- 定义了两个重要的类型 Type 和 Value。
- 任意接口值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两部分组成。
- 提供了 reflect.TypeOf 和 reflect.ValueOf 两个函数来获取任意对象的 Value 和 Type
reflect的基本使用
- reflect.ValueOf(obj) 返回obj的Value(如果要对obj进行修改的话传入变量地址)
- reflect.TypeOf(obj) 返回obj的对象类型
- reflect.Indirect(value) 返回Value获取了该指针指向的值
- reflect.New(typ Type) 返回一个新的type类型的对象
reflect的内置对象
Value
```go Value 表示一个Go对象的值 value.CanSet()返回值能否更改 value.MethodByName(“name”)//根据方法名查找方法返回Value value.FieldByName(“name”)//根据属性名查找属性返回Value
func (v Value) CanAddr() Value///这个方法是设置值的方法的基础,使用ValueOf()生成一个Value时,参数是值传递的,因此设置这个参数一点意义也没有。正确的方法是传入一个指针,然后调用Elem()方法来生成其指向的元素对应的Value对象 func (v Value) Addr() Value//获得其地址 如果CanAddr()返回false,那么这个调用会panic func (v Value) UnsafeAddr() uintptr//和Addr()方法有同样的要求 func (v Value) CanSet() bool//是否可以修改其值,一个值必须是可以获得地址且不能通过访问结构的非导出字段获得,方可被修改 func (v Value) Set(x Value) //设置值 如果CanSet()返回false,那么panic func (v Value) Convert(t Type) Value//转换为其他类型的值 如果无法使用标准Go转换规则来转换,那么panic func (v Value) Iterface{} interface{}//以空接口类型获得值 如果Value是通过访问结构的非导出字段获得,panic func (v Value) IsValid() bool // 是否是一个合法的Value对象 只有零值才会返回false func (v Value) Kind() Kind // 所属的类型分类 零值会返回Invalid func (v Value) String() string// 字符串格式 返回值的格式为<类型 值> func (v Value) Type() Type//类型
- 方法集和方法 func (v Value) NumMethod() int func (v Value) Method(index int) Value func (v Value) MethodByName(name string) Value //Type类型定义了同名方法,但是返回的是类型信息,这里返回的是值信息。Method()方法,如果v没有任何方法集,或者index越界,那么panic。MethodByName()方法,如果没有找到名为name的方法,那么返回一个零值
*属性 func (v Value) NumField() int //结构字段数量 func (v Value) Field(i int) Value //使用索引来访问字段,索引从0开始,如果越界将panic func (v Value) FieldByName(name string) Value //使用名称来访问字段,如果未找到那么返回false func (v Value)FieldByNameFunc(match func(string) bool) Value //访问名称使得match函数返回true的字段,在同一个内嵌层次上,只能有一个字段使得match返回true。如果同一层次上多个字段使得match返回true, 那么这些字段都认为是不符合要求的 func FieldByIndex(index []int) Value //这个方法使得访问结构的内嵌字段成为可能。将访问各个层次的字段的索引排列起来,就形成了一个[]int,参数index不可越界,否则panic
*函数类型值 func (v Value) Call(in []Value)[]Value func (v Value) CallSlice(in []Value) []Value 注:Call()方法用来调用函数(参数可变或者固定),采用的是用户代码使用的调用格式。CallSlice()方法专门用于调用参数可变的函数,它采用了编译器使用的调用格式。这两种调用格式的区别在于: u 对于参数固定的函数,两种格式没有任何区别,都是按照位置,将实参赋予形参 u 对于参数可变的函数,编译器格式会特别处理最后一个参数,将剩余的实参依次放入一个slice内,传递给可变形参的就是这个slice。 func (v Value) Pointer() uintptr //以uintptr返回函数的值,这个值并不能独一无二的识别一个函数,只是保证如果函数为nil,那么这个值为0
*类型信息 func ChanDir() ChanDir//判断通道的方向 func Elem() Type//元素的类型
*通道值 func (v Value) IsNil() bool func (v Value) Pointer() uintptr //以unitptr返回其值,没有使用unsafe.Pointer类型,所以不需要导入unsafe包 func (v Value) Close() func (v Value) Len() int //通道当前元素数量 func (v Value) Cap() int //通道的长度 func (v Value) Send(x Value) //发送一个值,x必表示一个可以赋值给通道元素类型的值 func (v Value) TrySend(x Value) bool //尝试以非阻塞的方式发送一个值,返回操作成功与否 func (v Value) Recv() (Value,bool) //接收一个值,如果通道已经关闭,返回一个Value零值。由于通道本身可能传输Value零值,所以必须额外使用一个布尔返回值来表示接收是否成功 func (v Value) TryRecv() (Value,bool) //尝试以非阻塞的方式接收一个值
*slice func Elem() Type //类型 func (v Value) Len() int func (v Value) Cap() int func (v Value) IsNil() bool func (v Value) Pointer() uintptr func (v Value) Index(i int) Value //访问某个元素 func (v Value) Slice(i,j int) Value //访问某个子slice,下标必须合法 func (v Value) Slice3(i,j,k) Value //以Go1.3引入的3下标格式访问某个子slice,下标必须合法 func (v Value) SetCap(i int) //要求i必须在[v.Len(),v.Cap()]之间 func (v Value) SetLen(i int) //i必须在[0,v.Cap()]之间
*映射类型 func (v Value) Len() int func (v Value) IsNil() bool func (v Value) MapKeys() []Value//返回所有的键值 func (v Value) MapIndex(key Value) Value func (v Value) SetMapIndex(key, x Value)//如果x是零值,那么表示删除一个元素 func (v Value) Pointer() uintptr
指针类型 Go语言提供了两种指针类型,一种是通过和其他类型复合而成,另一种是unsafe.Pointer func Elem() Type 值 func(v Value) IsNil() bool func(v Value) Elem() Value func(v Value) Pointer() uintptr
*数组 func Elem() Type func Len() int func(v Value) Len() int func(v Value) Slice(i,j int) Value func(v Value) Slice3(i, j, k int) Value//这两个方法要求v.CanAddr()返回true
*接口类型 func(v Value) IsNil() bool//判断接口是否为空 func(v Value) Elem() Value//返回接口包含的真实值 func(v Value) InterfaceData() [2]uintptr//这个方法的用法比较奇怪,还未能找到一个合适的例子
[]byte类型 func (v Value)Bytes() []bytes func (v Value)SetBytes(x []bytes)
字符串类型 func (v Value) SetString(x string)//设置字符串的值 func (v Value) Index(i int)Value//访问单个字节 func (v Value) Len() int//字符串的长度
<a name="WLaxB"></a>
### Type
```go
Type
func Size() uinptr //大小: 一个该类型的值存储所需要的内存大小,以字节为单位
func Name() string //名称: 该类型在其定义包中的名称,有些类型没有名称(比如数组、slice、函数等等),返回一个空字符串
func PkgPath() string //定义位置: 该类型的定义位置,也就是导入该类型使用的import语句的参数。如果该类型是预定义的(string, error等)或者无名的,那么返回一个空字符串
func Kind() Kind //种类: 该类型所属的种类,reflect包定义了Kind类型来表示各种类型。重命名一个类型并不会改变其种类
func NumMethod() int //方法集: 该类型的方法集,Type类型提供了方法来返回方法数量,访问各个方法。reflect包定义了Method类型来表示一个方法
func Method(index int) Method //使用索引来访问方法集,索引从0开始,如果越界,将panic
func MethodByName(name string) (Method, bool)//使用名称来访问某个方法,bool参数指示是否找到该方法
func Implements( u Type) bool //判断是否使用了接口u,u必须表示一个接口类型
func ConvertibleTo(u Type) bool //是否可以使用标准转换语句,转换为其他类型
func AssignableTo(u Type) bool //是否可以赋值给其他类型的变量
*函数类型
func IsVariadic() bool//参数是否可变
func NumIn() int //参数的数量,需要注意的是,可变参数单独作为slice计算
func NumOut() int //返回值的数量,需要注意的是,可变参数单独作为slice计算
func In(i int) Type //第i个参数,i从0开始
func Out(i int) Type //第i个返回值,i从0开始
StructField
结构的字段具有很多特殊信息,定义了StructField类型来表示一个字段
func NumField() int //结构字段数量
func Field(i int) StructField //使用索引来访问字段,索引从0开始,如果越界将panic
func FieldByName(name string) (StructField,bool) //使用名称来访问字段,如果未找到那么返回false
func FieldByNameFunc(match func(string) bool) (StructField,bool) //访问名称使得match函数返回true的字段,在同一个内嵌层次上,只能有一个字段使得match返回true。如果同一层次上多个字段使得match返回true,
那么这些字段都认为是不符合要求的
func FieldByIndex(index []int) StructField //这个方法使得访问结构的内嵌字段成为可能。将访问各个层次的字段的索引排列起来,就形成了一个[]int,参数index不可越界,否则panic
type StructField struct{
Name string //名称
PkgPath string //* 对于导出字段,为空字符串* 对于非导出字段,是定义该字段类型的包名
Type Type
Tag StructTag
Offset uintptr //在结构内的位移
Index []int //当使用Type.FieldByIndex()方法的参数
Anonymous bool //是否为匿名字段
}
StructTag
描述了结构字段的tag
tag格式为:
* 由多个部分连接而成,部分之间有可选的空格
* 部分格式为 key:value
* key是非空的字符串,由非控制字符组成,并且不可以是空格、双引号、冒号
* 值由双引号包围,遵循Go字符串字面值语法
func (tag StructTag) Get(key string) string:根据 Tag 中的键获取对应的值,例如`key1:"value1" key2:"value2"`的 Tag 中,可以传入“key1”获得“value1”。
func (tag StructTag) Lookup(key string) (value string, ok bool):根据 Tag 中的键,查询值是否存在。
Kind
reflect包使用Kind类型来表示类型所属的分类