内容简介:各位学习Go语言的朋友,周末好,这次跟大家聊一聊Go语言的一个高级话题:反射。这篇文章是从我过去的学习笔记修改来的,内容主要来自Go Blog的一篇文章《The law of reflection》。反射来自元编程,指通过类型检查变量本身数据结构的方式,只有部分编程语言支持反射。
各位学习 Go 语言的朋友,周末好,这次跟大家聊一聊Go语言的一个高级话题:反射。
这篇文章是从我过去的学习笔记修改来的,内容主要来自Go Blog的一篇文章《The law of reflection》。
这篇文章主要介绍反射和接口的关系,解释内在的关系和原理。
反射来自元编程,指通过类型检查变量本身数据结构的方式,只有部分编程语言支持反射。
类型
反射构建在类型系统之上,Go是静态类型语言,每一个变量都有 静态类型 ,在编译时就确定下来了。
比如:
type MyInt int var i int var j MyInt
i和j的 底层类型
都是 int
,但i的静态类型是 int
,j的静态类型是 MyInt
,这两个是不同类型,是不能直接赋值的,需要类型强制转换。
接口类型比较特殊,接口类型的变量被多种对象类型赋值,看起来像动态语言的特性,但变量类型始终是接口类型,Go是静态的。举例:
var r io.Reader r = os.Stdin r = bufio.NewReader(r) r = new(bytes.Buffer) // and so on
虽然r被3种类型的变量赋值,但r的类型始终是 io.Reader
。
最特别:空接口 interface{}
的变量可以被任何类型的值赋值,但类型一直都是 interface{}
。
接口的表示
Russ Cox(Go语言创始人)在他的 博客详细介绍了Go语言接口 ,结论是:
接口类型的变量存储的是 一对数据 :
- 变量实际的值
- 变量的静态类型
例子:
var r io.Reader tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } r = tty
r是接口类型变量,保存了 值tty和tty的类型
*os.File
,所以才能使用 类型断言
判断r保存的值的静态类型:
var w io.Writer w = r.(io.Writer)
虽然r中包含了tty和它的类型,包含了tty的所有函数,但r是接口类型,决定了r只能调用接口 io.Reader
中包含的函数。
记住:接口变量保存的不是接口类型的值,还是英语说起来更方便:Interfaces do not hold interface values.
反射的3条定律
定律1:从接口值到反射对象
反射是一种检测存储在接口变量中值和类型的机制。通过 reflect
包的一些函数,可以把接口转换为反射定义的对象。
掌握 reflect
包的以下函数:
-
reflect.ValueOf({}interface) reflect.Value
:获取某个变量的值,但值是通过reflect.Value
对象描述的。 -
reflect.TypeOf({}interface) reflect.Type
:获取某个变量的静态类型,但值是通过reflect.Type
对象描述的,是可以直接使用Println
打印的。 -
reflect.Value.Kind() Kind
:获取变量值的底层类型(类别),注意不是类型,是Int、Float,还是Struct,还是Slice, 具体见此 。 -
reflect.Value.Type() reflect.Type
:获取变量值的类型,效果等同于reflect.TypeOf
。
再解释下Kind和Type的区别,比如:
type MyInt int var x MyInt = 7 v := reflect.ValueOf(x)
v.Kind()得到的是Int,而Type得到是 MyInt
。
定律2:从反射对象到接口值
定律2是定律1的逆向过程,上面我们学了: 普通变量 -> 接口变量 -> 反射对象
的过程,这是从 反射对象 -> 接口变量
的过程,使用的是 Value
的 Interface
函数,是把实际的值赋值给空接口变量,它的声明如下:
func (v Value) Interface() (i interface{})
回忆一下:接口变量存储了实际的值和值的类型, Println
可以根据接口变量实际存储的类型自动识别其值并打印。
注意事项:如果Value是结构体的非导出字段,调用该函数会导致panic。
定律3:当反射对象所存的值是可设置时,反射对象才可修改
从定律1入手理解,定律3就不再那么难懂。
Settability is a property of a reflection Value, and not all reflection Values have it.
可设置指的是,可以通过Value设置原始变量的值。
通过函数的例子思考一下可设置:
func f(x int)
在调用f的时候,传入了参数x,从函数内部修改x的值,外部的变量的值并不会发生改变,因为这种是传值,是拷贝的传递方式。
func f(p *int)
函数f的入参是指针类型,在函数内部的修改变量的值,函数外部变量的值也会跟着变化。
使用反射也是这个原理,如果创建Value时传递的是变量,则Value是不可设置的。如果创建Value时传递的是变量地址,则Value是可设置的。
可以使用 Value.CanSet()
检测是否可以通过此Value修改原始变量的值。
x := 10 v1 := reflect.ValueOf(x) fmt.Println("setable:", v1.CanSet()) p := reflect.ValueOf(&x) fmt.Println("setable:", p.CanSet()) v2 := p.Elem() fmt.Println("setable:", v2.CanSet())
如何通过Value设置原始对象值呢?
Value.SetXXX()
系列函数可设置Value中原始对象的值。
系列函数有:
- Value.SetInt()
- Value.SetUint()
- Value.SetBool()
- Value.SetBytes()
- Value.SetFloat()
- Value.SetString()
- ...
设置函数这么多,到底该选用哪个Set函数?
根据 Value.Kind()
的结果去获得变量的底层类型,然后选用该类别的Set函数。
参考资料
- 如果这篇文章对你有帮助,请点个赞/喜欢,感谢 。
- 本文作者: 大彬
- 如果喜欢本文,随意转载,但请保留此原文链接: http://lessisbetter.site/2019/02/24/go-law-of-reflect/
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 黑客定律:对开发人员有用的定律、理论、原则和模式
- 「微服务系列 02」康威定律
- 技术公号和墨菲定律
- 开发六大定律,比如项目总会延期
- 摩尔定律谢幕,芯片的未来在哪?
- 微服务的灾难-康威定律和 KPI 冲突
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Android开发艺术探索
任玉刚 / 电子工业出版社 / 2015-9-1 / CNY 79.00
《Android开发艺术探索》是一本Android进阶类书籍,采用理论、源码和实践相结合的方式来阐述高水准的Android应用开发要点。《Android开发艺术探索》从三个方面来组织内容。第一,介绍Android开发者不容易掌握的一些知识点;第二,结合Android源代码和应用层开发过程,融会贯通,介绍一些比较深入的知识点;第三,介绍一些核心技术和Android的性能优化思想。 《Andro......一起来看看 《Android开发艺术探索》 这本书的介绍吧!