文章目录
Golang反射
反射简介
Go语言提供了一种机制,能够在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,而不需要在编译时就知道这些变量的具体类型。这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。
即:反射可以在运行期间,操作任意类型的对象。可以通过TypeOf
方法获得对象类型。通过ValueOf
获得对象值。
为何需要反射?
有时候我们需要编写一个函数能够处理一类并不满足普通公共接口的类型的值,也可能是因为它们并没有确定的表示方式,或者是在我们设计该函数的时候这些类型可能还不存在。
一个大家熟悉的例子是fmt.Fprintf
函数提供的字符串格式化处理逻辑,它可以用来对任意类型的值格式化并打印,甚至支持用户自定义的类型。让我们也来尝试实现一个类似功能的函数。为了简单起见,我们的函数只接收一个参数,然后返回和fmt.Sprint
类似的格式化后的字符串。我们实现的函数名也叫Sprint
。
我们首先用switch
类型分支来测试输入参数是否实现了String
方法,如果是的话就调用该方法。然后继续增加类型测试分支,检查这个值的动态类型是否是string
、int
、bool
等基础类型,并在每种情况下执行相应的格式化操作。
func Sprint(x interface{
}) string {
type stringer interface {
String() string
}
switch x := x.(type) {
case stringer:
return x.String()
case string:
return x
case int:
return strconv.Itoa(x)
// ...similar cases for int16, uint32, and so on...
case bool:
if x {
return "true"
}
return "false"
default:
// array, chan, func, map, pointer, slice, struct
return "???"
}
}
但是我们如何处理其它类似[]float64
、map[string][]string
等类型呢?我们当然可以添加更多的测试分支,但是这些组合类型的数目基本是无穷的。还有如何处理类似url.Values
这样的具名类型呢?即使类型分支可以识别出底层的基础类型是map[string][]string
,但是它并不匹配url.Values
类型,因为它们是两种不同的类型,而且switch类型分支也不可能包含每个类似url.Values
的类型,这会导致对这些库的依赖。
没有办法来检查未知类型的表示方式,我们被卡住了。这就是我们为何需要反射的原因。
reflect包
反射是由 reflect 包提供的。 它定义了两个重要的类型, Type
和 Value
. 任意接口值在反射中都可以理解为由 reflect.Type
和 reflect.Value
两部分组成,并且 reflect 包提供了 reflect.TypeOf
和 reflect.ValueOf
两个函数来获取任意对象的 Value 和 Type。
Type接口源码分析
源码:
type Type interface {
// 内存字节对齐
Align() int
// 结构体内存字节对齐
FieldAlign() int
// 结构体实现的方法,必须是公共方法且首字母大写
Method(int) Method
//根据方法名获得方法
MethodByName(string) (Method, bool)
// 方法数量
NumMethod() int
// 类型名称
Name() string
// 包路径
PkgPath() string
// 需要存储的字节数
Size() uintptr
// 类型的字符串描述
String() string
// 返回此类型的具体种类
Kind() Kind
// 判断是否实现了某一个接口
Implements(u Type) bool
// 判断是否可以赋值给u
AssignableTo(u Type) bool
// 是否可以转换为u类型
ConvertibleTo(u Type) bool
// 是否可以比较
Comparable() bool
// 类型的字节长度
Bits() int
// 通道类型的方向
ChanDir() ChanDir
// 函数类型是否是可变参数
IsVariadic() bool
// 返回类型的元素类型:Array, Chan, Map, Pointer, or Slice
Elem() Type
// 返回结构体的第几个字段
Field(i int) StructField
// 根据索引获得字段
FieldByIndex(index []int) StructField
// 根据名称获得字段
FieldByName(name string) (StructField, bool)
// 根据函数名称返回结构体字段
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 函数类型的第几个参数
In(i int) Type
// 返回map类型的key
Key() Type
// 返回数组类型的长度
Len() int
// 返回结构体类型的字段数
NumField() int
// 返回函数类型的输入参数数量
NumIn() int
// 函数类型返回值的数量
NumOut() int
// 函数类型的第几个返回值
Out(i int) Type
}
Value源码分析
type Value struct {
// Has unexported fields.
}
Value 是 Go 值的反射接口。
并非所有方法都适用于所有类型的值。每种方法的文档中都会注明限制条件(如果有)。
在调用特定于种类的方法之前,使用 Kind 方法找出值的种类。调用不适合该类型的方法会导致运行时panic。
零值表示没有值。
它的 IsValid 方法返回 false,
它的 Kind 方法返回 Invalid,
它的 String 方法返回“< invalid Value >”,
所有其他方法都panic。
大多数函数和方法从不返回无效值。如果是这样,它的文档会明确说明条件。
一个 Value 可以被多个 goroutine 同时使用,前提是底层的 Go 值可以同时用于等效的直接操作。
要比较两个值,请比较 Interface 方法的结果。对两个值使用 == 不会比较它们所代表的基础值。
value.go中的函数
调用方法:
reflect.XXX
func Append(s Value, x ...Value) Value
添加值到切片
func AppendSlice(s, t Value) Value
添加一个切片到切片
func Indirect(v Value) Value
返回v的指针值
func MakeChan(typ Type, buffer int) Value
创建一个通道,返回value类型
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
创建一个函数
func MakeMap(typ Type) Value
创建一个map
func MakeMapWithSize(typ Type, n int) Value
创建一个n大小的map
func MakeSlice(typ Type, len, cap int) Value
创建一个切片
func New(typ Type) Value
创建一个新的类型
func NewAt(typ Type, p unsafe.Pointer) Value
在某个指针地址处创建一个类型
func ValueOf(i any) Value
值类型
func Zero(typ Type) Value
零值的描述
Value结构体方法
func (v Value) Addr() Value
通常用于获取一个指向结构体字段或slice元素,为了调用一个方法需要一个指针receiver
func (v Value) Bool() bool
返回底层的值,如果v的kind不是bool则会panic
func (v Value) Bytes() []byte
返回底层的值,如果v的底层值不是一个字节切片则会panic
func (v Value) Call(in []Value) []Value
反射函数的值,并调用
func (v Value) CallSlice(in []Value) []Value
同上
func (v Value) CanAddr() bool
检查v是否是可寻址的