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

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

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

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

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

type MyInt intvar a int
var b MyInt

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

type emptyInterface struct {typ  *rtypeword unsafe.Pointer
}

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

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

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

// rtype is the common implementation of most values.
// It is embedded in other struct types.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {size       uintptrptrdata    uintptr // number of bytes in the type that can contain pointershash       uint32  // hash of type; avoids computation in hash tablestflag      tflag   // extra type information flagsalign      uint8   // alignment of variable with this typefieldAlign uint8   // alignment of struct field with this typekind       uint8   // enumeration for C// function for comparing objects of this type// (ptr to object A, ptr to object B) -> ==?equal     func(unsafe.Pointer, unsafe.Pointer) boolgcdata    *byte   // garbage collection datastr       nameOff // string formptrToThis typeOff // type for pointer to this type, may be zero
}

定义了类型需要的数据。

1.1 reflect中的结构体

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

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

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

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

type Value struct {// typ holds the type of the value represented by a Value.typ *rtype// Pointer-valued data or, if flagIndir is set, pointer to data.// Valid when either flagIndir is set or typ.pointers() is true.ptr unsafe.Pointer// flag holds metadata about the value.// The lowest bits are flag bits:// - flagStickyRO: obtained via unexported not embedded field, so read-only//  - flagEmbedRO: obtained via unexported embedded field, so read-only//   - flagIndir: val holds a pointer to the data//  - flagAddr: v.CanAddr is true (implies flagIndir)// - flagMethod: v is a method value.// The next five bits give the Kind of the value.// This repeats typ.Kind() except for method values.// The remaining 23+ bits give a method number for method values.// If flag.kind() != Func, code can assume that flagMethod is unset.// If ifaceIndir(typ), code can assume that flagIndir is set.flag// A method value represents a curried method invocation// like r.Read for some receiver r. The typ+val+flag bits describe// the receiver r, but the flag's Kind bits say Func (methods are// functions), and the top bits of the flag give the method number// in r's type's method table.
}

1.2 reflect对象的关系

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

反射对象关系图

1) 从接口值到反射对象

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

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

2) 从反射对象到接口值

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

var f float64 = 3.1415
v := reflect.ValueOf(f)   // f 隐式地被转成了interface{}
y := v.Interface().(float64) // y的类型是float64

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

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

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

var f float64 = 3.1415
v := reflect.ValueOf(f)   // f 隐式地被转成了interface{}
v.SetFloat(2.873)      // Error: 发生Panic

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

panic: reflect.Value.SetFloat using unaddressable value

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

var f float64 = 3.1415
v := reflect.ValueOf(&f)   // 传了f的指针,&f 隐式地被转成了interface{}
v.SetFloat(2.873)      // 成功修改

综上,也就是说当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 (v Value) Addr() Value
func (v Value) Bool() bool
func (v Value) Bytes() []byte
func (v Value) Call(in []Value) []Value// 最后会进入汇编代码进行方法调用
func (v Value) CallSlice(in []Value) []Value
func (v Value) CanAddr() bool
func (v Value) CanInterface() bool
func (v Value) CanSet() bool
func (v Value) Cap() int
func (v Value) Close()
func (v Value) Complex() complex128
func (v Value) Convert(t Type) Value
func (v Value) Elem() Value
func (v Value) Field(i int) Value
func (v Value) FieldByIndex(index []int) Value
func (v Value) FieldByName(name string) Value
func (v Value) FieldByNameFunc(match func(string) bool) Value
func (v Value) Float() float64
func (v Value) Index(i int) Value
func (v Value) Int() int64
func (v Value) Interface() (i interface{})
func (v Value) InterfaceData() [2]uintptr
func (v Value) IsNil() bool
func (v Value) IsValid() bool
func (v Value) IsZero() bool
func (v Value) Kind() Kind
func (v Value) Len() int
func (v Value) MapIndex(key Value) Value
func (v Value) MapKeys() []Value
func (v Value) MapRange() *MapIter
func (v Value) Method(i int) Value
func (v Value) MethodByName(name string) Value
func (v Value) NumField() int
func (v Value) NumMethod() int
func (v Value) OverflowComplex(x complex128) bool
func (v Value) OverflowFloat(x float64) bool
func (v Value) OverflowInt(x int64) bool
func (v Value) OverflowUint(x uint64) bool
func (v Value) Pointer() uintptr
func (v Value) Recv() (x Value, ok bool)
func (v Value) Send(x Value)
func (v Value) Set(x Value)
func (v Value) SetBool(x bool)
func (v Value) SetBytes(x []byte)
func (v Value) SetCap(n int)
func (v Value) SetComplex(x complex128)
func (v Value) SetFloat(x float64)
func (v Value) SetInt(x int64)
func (v Value) SetLen(n int)
func (v Value) SetMapIndex(key, elem Value)
func (v Value) SetPointer(x unsafe.Pointer)
func (v Value) SetString(x string)
func (v Value) SetUint(x uint64)
func (v Value) Slice(i, j int) Value
func (v Value) Slice3(i, j, k int) Value
func (v Value) String() string
func (v Value) TryRecv() (x Value, ok bool)
func (v Value) TrySend(x Value) bool
func (v Value) Type() Type
func (v Value) Uint() uint64
func (v Value) UnsafeAddr() uintptr

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原理相关推荐

  1. go结构体初始化_golang中结构体的初始化方法

    目录 1.自定义一个结构体 type Vertex struct { X, Y float64 } 2.初始化方法-指针: rect1 := new(Vertex ) rect2 := &Ve ...

  2. go结构体初始化_golang中结构体的初始化方法(new方法)

    自定义一个结构体 type Rect struct { x, y float64 width, height float64 } 初始化方法: rect1 := new(Rect) rect2 := ...

  3. go结构体初始化_golang中结构体的初始化方法(new方法) | 学习笔记

    自定义一个结构体 1 2 3 4type Rect struct { x, y float64 width, height float64 } 初始化方法: 1 2 3 4rect1 :=new(Re ...

  4. go结构体初始化_浅谈golang结构体偷懒初始化

    运行一段程序,警告: service/mysqlconfig.go:63::error: golang.guazi-corp.com/tools/ksql-runner/model.CreatingM ...

  5. golang 结构体简介

    Go 通过类型别名(alias types)和结构体的形式支持用户自定义类型,或者叫定制类型.试图表示一个现实世界中的实体. 结构体由一系列命名的元素组成,这些元素又被称为字段,每个字段都有一个名称和 ...

  6. C语言试题五十二之学生的记录由学号和成绩组称个,n名大学生得数据已在主函数中放入结构体数组a中,请编写函数fun,它的功能时:按分数的高低排列学生的记录,高分在前。

    1. 题目 请编写一个函数void function(Student a[], int n),其功能时:学生的记录由学号和成绩组称个,n名大学生得数据已在主函数中放入结构体数组a中,请编写函数fun, ...

  7. c语言结构体在内存中的存储,C语言结构体在内存中的存储情况探究------内存对齐...

    条件(先看一下各个基本类型都占几个字节): voidsize_(){ printf("char类型:%d", sizeof(char)); printf("int类型:% ...

  8. c语言 由函数组成的数组,学生的记录由学号和成绩组成,N名学生的数据已在主函数中放入结构体数组s中,请编写函数fun(),它的_考题宝...

    学生的记录由学号和成绩组成,N名学生的数据已在主函数中放入结构体数组s中,请编写函数fun(),它的功能是按分数的高低排列学生的记录,低分在前. 注意:部分源程序给出如下. 请勿改动主函数main和其 ...

  9. linux查看内存条pn,实验:使用GDB查看结构体在内存中的存储方式

    结构体在内存中的表示形式是怎么样的? 结构体在内存中和普通变量存储没有太大的区别. 首先我们看看,计算机如何读取普通变量:   普通变量例如int是占据4个字节,计算机读内存的时候会从起始地址开始读, ...

最新文章

  1. 好多Javascript日期选择器呀-5
  2. szu cf集训Codeforces Round #631 (Div. 2)A ~ D[贪心,数据结构,思维,dp]
  3. 深入探讨 Java 类加载器
  4. caffe配置中的一些问题
  5. 正负样本不平衡处理方法总结
  6. [图像处理] 直方图均衡化原理 - 数学推导
  7. vue --- 使用animate.css实现动画
  8. boid模型的Matlab程序,动物集群运动行为模型系列之五-—本科毕业设计.doc
  9. leetcode95. 不同的二叉搜索树 II(递归)
  10. SpringMVC源码解析(四)——请求处理
  11. LeetCode算法入门- Longest Palindromic Substring-day5
  12. java synchronized静态同步方法与非静态同步方法,同步语句块
  13. 树莓派python开发教程_树莓派教程(基于python编程)--入门篇
  14. itertools chain
  15. cf服务器延迟测试,Cloudflare-SpeedTest - 测试 CF CDN 延迟和速度,CF自选IP
  16. 计算机的磁盘碎片是什么,什么是磁盘的碎片化
  17. 21个令程序员泪流满面的瞬间 ...
  18. 面试复习归纳(技术服务、网络安全、运维与云计算)
  19. 快手小店通涨粉推广怎么做?
  20. linux下查看系统内存使用情况的几个命令

热门文章

  1. 1 Hadoop简介
  2. Android Jetpack组件之数据库Room详解(二)
  3. JavaScript浮点运算0.2+0.1 !== 0.3
  4. Java反射机制:表单数据自动封装到JavaBean中【IT】
  5. 电脑公司 Ghost XP SP3 国庆特别版 v2011.10
  6. 关于工作的选择之软件开发还是软件维护的建议
  7. 让人吐血的文章,要被气死了
  8. vue图片压缩不失真_图片压缩会失真?快试试这几个无损压缩神器。
  9. leetcode 896. 单调数列
  10. leetcode剑指 Offer 53 - II. 0~n-1中缺失的数字(二分查找)