Go的reflect机制和reflect包
概述
虽然Go是静态语言,然而还是提供了reflect机制,并且定义了reflect包来辅助反射处理。在reflect包中,最重要的两个类型就是Type和Value,分别从类型、值的角度来描述一个Go对象。
Type类型是一个接口,这个接口实现了String() string方法。Value类型是一个结构,但是并未定义任何导出字段,同样定义了String() string方法。
使用如下语句来导入reflect包
import "reflect"
注意:
1.本文中出现在类型章节的函数,都是Type的方法。由于接口的特殊性,无法明确其receiver是指针还是值,所以并未显示其receiver,但都是有receiver的
2.很多方法都有要求,如果要求不满足,那么panic
3.Value类型也有零值,零值只能调用IsValid()/String()/Kind()方法,其余方法都会panic
下面我们将依次介绍不同类型的对象和它对应的Type/Value对象
Go的reflect机制和reflect包 1
类型 3
Kind类型 4
值 4
算术类型的Go对象: 6
类型 6
值 6
结构类型的Go对象 7
类型 7
StructField类型 7
StructTag类型 8
值 8
方法类型的Go对象 8
类型 8
值 9
通道类型的Go对象 9
类型 9
值: 10
Slice类型的Go对象 10
类型 11
值 11
映射类型的Go对象 11
类型 11
值 12
指针类型的Go对象 12
类型 12
值 12
数组类型的Go对象 12
类型 12
值 12
接口类型的Go对象 13
值 13
[]byte类型的Go对象 13
值 13
字符串类型的Go对象 13
值 13
组合生成一个Value 13
组合生成一个Type 15
对于任何类型的Go对象而言,类型和值都是其运行时的相关信息,使用TypeOf()/ValueOf()函数获得表示类型/值信息。
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
类型
* 对齐信息: 包括作为变量时的对齐要求和作为一个结构字段时的对齐信息
* 大小: 一个该类型的值存储所需要的内存大小,以字节为单位
* 名称: 该类型在其定义包中的名称,有些类型没有名称(比如数组、slice、函数等等),返回一个空字符串
* 定义位置: 该类型的定义位置,也就是导入该类型使用的import语句的参数。如果该类型是预定义的(string, error等)或者无名的,那么返回一个空字符串
* 种类: 该类型所属的种类,reflect包定义了Kind类型来表示各种类型。重命名一个类型并不会改变其种类
* 方法集: 该类型的方法集,Type类型提供了方法来返回方法数量,访问各个方法。reflect包定义了Method类型来表示一个方法
//使用索引来访问方法集,索引从0开始,如果越界,将panic
func MethodByName(name string) (Method, bool)
func ConvertibleTo(u Type) bool
func AssignableTo(u Type) bool
Kind类型
reflect包使用Kind类型来表示类型所属的分类,其定义如下:
type Kind uint
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
Kind()类型定义了String() string方法,提供一个字符串表示形式
值
Value类型表示一个Go对象的值,对于任意类型的Go对象,以下方法都是可用的
* 是否可以获得地址。如果一个值来自以下途径,那么可以获得其地址
func (v Value) CanAddr() Value
//这个方法是设置值的方法的基础,使用ValueOf()生成一个Value时,参数是值传递的,因此设置这个参数一点意义也没有。正确的方法是传入一个指针,然后调用Elem()方法来生成其指向的元素对应的Value对象
//如果CanAddr()返回false,那么这个调用会panic
func (v Value) UnsafeAddr() uintptr
//和Addr()方法有同样的要求
* 是否可以修改其值,一个值必须是可以获得地址且不能通过访问结构的非导出字段获得,方可被修改
func (v Value) Convert(t Type) Value
func (v Value) Iterface{} interface{}
//如果Value是通过访问结构的非导出字段获得,panic
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) String() string
算术类型的Go对象:
类型
值
func (v Value) Float() float64
func (v Value) Complex() complex128
func (v Value) SetFloat(x float64)
func (v Value) SetInt(x int64)
func (v value) SetUnt(x uint64)
func (v Value) SetComplex(x complex128)
* 辅助设置值:由于每个Setxxx方法都是对应了多个具体的基本类型,因此需要一个方法来判断设置值是否能够长度,通过判断值是否可以存储在Valud对象中且不溢出
func (v Value) OverflowFloat(x float64) bool
func (v Value) OverflowInt(x int64) bool
func (v value) OverflowUnt(x uint64) bool
func (v Value) OverflowComplex(x complex128) bool
结构类型的Go对象
类型
结构对象的类型就是字段的数量和类型,但结构的字段具有很多特殊信息,reflect包定义了StructField类型来表示一个字段
func FieldByName(name string) (StructField,bool)
func FieldByNameFunc(match func(string) bool) (StructField,bool)
//访问名称使得match函数返回true的字段,在同一个内嵌层次上,只能有一个字段使得match返回true。如果同一层次上多个字段使得match返回true,那么这些字段都认为是不符合要求的
func FieldByIndex(index []int) StructField
//这个方法使得访问结构的内嵌字段成为可能。将访问各个层次的字段的索引排列起来,就形成了一个[]int,参数index不可越界,否则panic
StructField类型
Name string //名称
PkgPath string
Offset uintptr //在结构内的位移
Index []int //当使用Type.FieldByIndex()方法的参数
Anonymous bool //是否为匿名字段
StructTag类型
StructTag类型也是一个结构,描述了结构字段的tag。按照约定,tag格式为:
* key是非空的字符串,由非控制字符组成,并且不可以是空格、双引号、冒号
func (tag StructTag) Get(key string) string
值
Value类型为结构类型的Go对象几乎定义了相同的方法,唯一的不同是这些方法返回的是一个Value,而不是StructField。Value类型有零值,因此返回bool值就没有意义了
func (v Value) Field(i int) Value
func (v Value) FieldByName(name string) Value
func (v Value)FieldByNameFunc(match func(string) bool) Value
//访问名称使得match函数返回true的字段,在同一个内嵌层次上,只能有一个字段使得match返回true。如果同一层次上多个字段使得match返回true,那么这些字段都认为是不符合要求的
func FieldByIndex(index []int) Value
//这个方法使得访问结构的内嵌字段成为可能。将访问各个层次的字段的索引排列起来,就形成了一个[]int,参数index不可越界,否则panic
方法类型的Go对象
类型
对于一个函数类型,所有的信息都在其签名中,那就是参数的数量、类型以及返回值的数量、类型。
Type类型定义了如下方法,提供这些信息
func IsVariadic() bool
//参数是否可变
func NumIn() int
func NumOut() int
//参数、返回值的数量,需要注意的是,可变参数单独作为slice计算
func In(i int) Type
func Out(i int) Type
//第i个参数/返回值,i从0开始
值
对于一个函数类型值,Value类型提供了2个方法来调用这个值,也就是调用函数。
在reflect包中,Value类型被定义为一个结构,并且没有任何的导出字段,所有的信息都是通过方法调用给出的。
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
通道类型的Go对象
Go语言的通道对象信息可以分为两类:
l 静态信息: 通道的方向和元素类型
l 动态信息: 发送、接收,通道的容量和当前元素数量
类型
Value对象定义了了以下方法来获得通道类型的类型信息
func ChanDir() ChanDir
//判断通道的方向
func Elem() Type
//元素的类型
ChanDir
ChanDir表示通道的方向,其定义如下
type ChanDir int
func (chan ChanDir)String() string
reflect包定义了一组常量来表示各种方向
const (
RecvDir ChanDir =1<<iota
SendDir
BothDir
)
值:
对于一个通道值,Value定义了如下方法
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类型的Go对象
在1.2版本中,每个Slice的真实类型为
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
在官方文档中这样写明:这个格式可能会在后续的版本中改变。Data字段并不足够确保它引用的数据不会被回收,程序应该以正确的格式来另外保存一个指向底层数据的指针
Go语言的slice对象信息可以分为两类:
n 静态信息: 元素类型
n 动态信息: 长度和容量
类型
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()]之间
映射类型的Go对象
映射类型的Go对象信息可以分为两类:
l 静态信息: 键类型和元素类型
l 动态信息: 键值、元素值以及映射的大小
类型
func Key() Type
func Elem() Type
值
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对象
Go语言提供了两种指针类型,一种是通过*和其他类型复合而成,另一种是unsafe.Pointer
类型
func Elem() Type
值
func(v Value) IsNil() bool
func(v Value) Elem() Value
func(v Value) Pointer() uintptr
数组类型的Go对象
类型
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
接口类型的Go对象
值
func(v Value) IsNil() bool
//判断接口是否为空
func(v Value) Elem() Value
//返回接口包含的真实值
func(v Value) InterfaceData() [2]uintptr
//这个方法的用法比较奇怪,还未能找到一个合适的例子
[]byte类型的Go对象
[]byte类型应用非常广泛,几乎所有涉及到数据的操作都会使用这个类型。Value类型特别为这个类型定义了相关操作
值
func (v Value)Bytes() []bytes
func (v Value)SetBytes(x []bytes)
字符串类型的Go对象
值
func (v Value) SetString(x string)
//设置字符串的值
func (v Value) Index(i int)Value
//访问单个字节
func (v Value) Len() int
//字符串的长度
组合生成一个Value
当我们已有某个Value值,可以使用以下方法来组合生成新的Value对象
func Append(s Value,value ...Value) Value
func AppendSlice(s, t Value) Value
* 前者将value参数附加到s参数中,返回结果
* 后者将t参数的元素附加到s参数末尾,返回结果
* 很显然,以值传递参数,参数本身的值并未改变
* s,t 必须都是表示slice,并且遵循Go的slice规则,即元素类型必须可赋值(前者)/相同(后者)
func Indirect(s Value) Value
* 如果s表示一个指针,那么返回所指向的元素,特别地,nil指针返回一个零值的Value
* 如果s表示其他类型值,那么返回s本身
或者使用一个Type类型,生成表示该类型值的Value,当然生成的都是零值
func MakeChan(typ Type, buffer int) Value
生成一个通道
func MakeMap(typ Type) Value
生成一个映射
func MakeSlice(typ Type, len, cap int) Value
生成一个slice
func New(typ Type) Value
生成一个类型为typ的零值,返回的是其指针,符合New的语义
func NewAt(typ Type, p unsafe.Pointer) Value
将p当做一个指向typ类型的指针,包装并返回
func Zero(typ Type) Value
生成typ类型的零值,这里其实是返回特殊值,返回值既不可以修改也不可以取得其地址
func MakeFunc(typ Type, fn func(args []Value)(results []Value)) Value
从某个函数类型typ生成一个Value,返回值wrap了第二个参数。返回值的类型为typ,但真实操作却由fn完成。返回值在调用时,会发生以下操作:
1.将所有参数转换为Value,并且加入到fn的args中。特别地,可变参数会先转换为一个slice,然后作为args的最后一个参数加入。
2.调用fn
3.将fn的返回值转换为typ中描述的类型,并返回
fn的签名类型非常特殊,允许任何数量、类型的参数,返回任何数量、类型的返回值,因此任何函数都可以包装在这样的签名里。真实的签名由typ提供,从而提供了动态生成函数的机制。
go文档提供了一个很好的例子,但这个函数的应用非常之广泛,就靠大家来探索了。
组合生成一个Type
当我们已经有一个Type值,可以使用以下方法来组合生成一个新的Type对象
func SliceOf(t Type) Type
func ChanOf(dir ChanDir, t Type) Type
func MapOf(key, value Type) Type
func PtrTo(t Type) Type
这些函数都非常简单,不需要什么特殊说明
有疑问加站长微信联系(非本文作者)