天天看点

golang 反射_Golang中的reflect原理

golang 反射_Golang中的reflect原理

反射(reflect)是在计算机程序运行时,访问,检查,修改它自身的一种能力,是元编程的一种形式。在Java等语言中都很好地支持了反射。Golang也实现了反射,主要核心位于reflect包,官方文档为:

https://golang.org/pkg/reflect/​golang.org

本文将主要介绍Golang中的反射原理和支持的反射操作。

1. reflect原理:结构体与关系

Golang是强类型语言,每一个对象都有具体的静态类型。为什么说是静态类型呢?举个例子,如下代码:

type 
           

a和b在Go中会被认为是不同的类型,即不会被隐式转换。另外,在Golang中的对象其实是同时记录了两个信息:变量的真实值,与该变量的类型描述。具体地,interface {}在内部是通过emptyInterface结构体表示的,结构体的定义如下:

type 
           

其中, typ为类型信息,word为指针。

另外interface{}是一个没有函数定义的接口定义,Golang中的继承实现是通过比较函数判断的。也就是说,所有的结构体都实现了默认接口interface{},这也是为什么所有值都能够隐式赋值给interface{}的原因。

以上的结构体便是Golang中反射的核心,也就是通过操作该结构来进行反射运算,包括获取对象的类型信息(rtype)和具体值(word指针),rtype的定义如下:

// rtype is the common implementation of most values.
           

定义了类型需要的数据。

1.1 reflect中的结构体

在Golang的反射中,另外两个核心结构体是Type和Value。

Type是描述类型信息的接口,包括:结构体的对齐方式、方法、字段、包路径、与其他结构体的关系等,具体定义可以参考源码:

https://golang.org/src/reflect/type.go?s=1310:7552#L27​golang.org

Value是保存了对象的类型、指针和其他元数据,具体定义如下:

type 
           

1.2 reflect对象的关系

reflect中的对象关系如下图所示,Type和Value称为反射对象,interface{}和Special Type是应用程序中的对象,其中,Special Type指应用程序中的具体类型。具体关系如下:

golang 反射_Golang中的reflect原理

反射对象关系图

1) 从接口值到反射对象

interface{} -> Type: 通过reflect.TypeOf(interface{})获得interface的类型信息对象;

interface{} -> Value:通过reflect.ValueOf(interface{})获得interface的Value反射类型对象;

2) 从反射对象到接口值

Value->interface{}:通过Value.Interface()方法可以获得值对象Value对应的接口;注意,这里不能够直接获得具体类型,如果要获得具体类型,还需要显式地进行转换。例如,

var 
           

其中1)和2)两条关系也是Golang反射中三条大规则中的前两条。另外第三条是:

3) 想要修改一个反射对象,那么该值必须是可以被设置的

这个可以一个例子进行说明。如下:

var 
           

以上在最后一行代码将会抛出异常:

panic: reflect.Value.SetFloat using unaddressable value
           

也就是说,Value v指向的不是一个可寻址的值,简单地说就是不是一个地址块。但是如果改成如下代码:

var 
           

综上,也就是说当Value中管理的值是一个可被寻址的值那么改置便是一个可被修改的Value。

或者换一个方式去理解,在Golang中方法调用是值传递,然后,假如我们想要该一个方法中修改某一个对象的值,那么我们应该将指向该值的指针传入,而不是直接将值传入。

4) Type/Value转换

Value->Type:可以通过Value.Type()方法获得;而Type->Value是指创建一个Type的实例对象,则可以通过reflect.New(typ)等方法创建。

2. reflect中的结构体与方法

这里分五个维度进行介绍reflect中的结构和方法,便于理解反射的使用方法。这些操作最终都会落到前面定义的结构体emptyInterface,除在外层封装中变能够确定的方法外。

2.1 结构体

reflect中的结构体主要包括:Type,Value,ChanDir,Kind,MapIter,Method,SelectCase,SelectDir,SliceHeader,StringHeader,StructField,StructTag,ValueError等。其中,Type和Value之前已经介绍过了。

  • ChanDir:管道的方向,有三个值:RecvDir/SendDir/BothDir,分别为接受,发送,双向;
  • Kind:Type中的类型信息,包括:Invalid, 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,
  • MapIter:Map的迭代器,包括三个方法:Key、Value、Next
  • Method:描述方法的信息,包括:方法名,包路径,类型,函数,所处的下表;
  • SelectCase:描述select 操作的信息,case的方向SelectDir,使用的Channel,发送的值Send;
  • SelectDir:描述SelectCase中的方向,有三个值:SelectSend/SelectRecv/SelectDefault
  • SliceHeader:描述切片Slice的信息,包括指针,长度,容量;
  • StringHeader:描述字符串string的信息,包括指针,长度;
  • StructField:描述结构体中的域field中的信息,包括:域名,包路径,类型,标签Tag,在结构体中的偏移量offset,Type.FieldByIndex中的下标index,是否是匿名;
  • StructTag:描述标签信息,有两个方法:Get、Lookup
  • ValueError:在调用一个Value不支持的方法时会报错,并记录到ValueError中。

2.2 reflect静态方法

reflect的静态方法主要用于反射对象的的操作,包括如下:

  • reflect.Copy(dst, src Value) int:将src对象(Slice或Array)复制给dst对象,返回复制的个数;
  • func DeepEqual(x, y interface{}) bool:比较两个对象是否是“深度相等”。具体如何比较可以参考:https://golang.org/pkg/reflect/#DeepEqual
  • func Swapper(slice interface{}) func(i, j int):生成一个Swapper交换方法,必须为slice;

2.3 域Type相关的方法

该类方法主要定义在https://golang.org/src/reflect/type.go?s=78900:78939#L2811中,包括,返回值都是Type:

  • reflect.ArrayOf:创建一个指定Type和个数的数组;
  • reflect.ChanOf:创建一个类型和方向的管道类型;
  • reflect.FuncOf:创建一个指定输入/输出/是否可变(variadic)的函数定义;
  • reflect.MapOf:创建一个指定key/value类型的Map类型;
  • reflect.PtrTo:创建一个类型的指针;
  • reflect.SliceOf:创建一个类型的Slice类型;
  • reflect.StructOf:创建一个指定StructField 列表的结构体定义类型;
  • reflect.TypeOf:获得interface的类型;

2.4 值Value相关静态方法

该类方法主要定义在https://golang.org/src/reflect/value.go?s=60334:60372#L2014中,包括:

  • reflect.Append:将值append到一个Slice中,并且返回结果;
  • reflect.AppendSlice:将一个slice append到slice中;
  • reflect.Indirect:返回该Value的指向对象,如果是nil,返回零值,如果非指针,返回该值;
  • reflect.MakeChan:创建一个执行类型和大小的channel;
  • reflect.MakeFunc:在指定类型上创建一个指定定义的函数;
  • reflect.MakeMap:创建一个指定类型的map;
  • reflect.MakeMapWithSize:同上,指定大小;
  • reflect.MakeSlice:创建Slice;
  • reflect.New:创建一个指定类型的实例对象;
  • reflect.NewAt:指定了指针类型?
  • reflect.Select:创建一个select操作,需指定SelectCase;
  • reflect.ValueOf:获得接口interface{}的Value;
  • reflect.Zero:创建一个指定类型的零值;

2.5 Value的实例方法

这里将不一一介绍Value的实例方法,大致可以分成三类:

  • 判断性方法:判断是否具有某些特性/能力;
  • 访问性方法:访问值的一些属性;
  • 修改性方法:修改Value中特性/值的方法;

这里介绍一个函数Elem(),该函数返回的是interface{}中包含的值或指针指向的值,如果value的类型不是reflect.Ptr,那么将返回零值。

具体如下:

func 
           

3. 总结

最后简单总结一下,本文首先介绍了Golang中反射的原理,包括其中的核心结构体和关系;然后介绍了Golang中reflect包下包含的结构体,静态函数,Type静态函数,Value静态函数和Value的实例函数。

参考

https://golang.org/pkg/reflect/https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-reflect/

Wenguang Liu:Golang中Routine闭包中的一个坑​zhuanlan.zhihu.com

golang 反射_Golang中的reflect原理