前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go语言反射

Go语言反射

作者头像
Steve Wang
发布2021-01-13 09:43:59
5660
发布2021-01-13 09:43:59
举报
文章被收录于专栏:从流域到海域从流域到海域

广义上来讲,反射是指计算机程序在运行时(run time)可以访问、检测和修改它本身状态或行为的一种能力。也即是说,反射就是程序在运行的时候能够"观察"并且修改自己的行为。 Go语言不是严格的面向对象的语言,虽然它也能够通过接口结构体实现接口的方法三者在某种程度上实现面向对象的一些特性,但Go语言的反射机制不像Java的反射机制那样。 Java反射机制实现的功能是:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意一个方法和查看并修改其属性。 Go语言的反射机制提供了在运行时更新变量和检查它们的值,调用他们的方法,但是在编译时并不知道这些变量的具体类型。这是因为Go语言中没有统一的面向对象编程的定义,对象就是简单的一个值或者变量。

为什么要使用反射?使用反射有哪些缺点?

需要反射的两个常见场景:
  1. 我们需要编写函数实现某种功能,但是没有约定好传入的参数类型是什么或者也可能是传入的类型很多,不能统一的表示。这时可以使用反射来得到参数的实际类型。
  2. 在程序运行期间可能需要根据某些条件来决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数参数进行反射,在运行期间动态地执行函数。

这是Go语言的场景,其他语言可能还包括动态代理什么的。

使用反射的缺点:
  1. 反射相关代码,可读性非常低。在软件工程中,可读性也是一个非常重要的指标。
  2. Go语言是一门静态强类型语言,编码过程中,编译器本身就能够提前发现一些类型错误,但对于反射代码是无能为力的。包含反射相关的代码,很可能会运行很久才会出错,这是往往是直接panic,可能会造成比较严重的后果。
  3. 反射对程序性能的影响很大,比正常代码运行速度慢一到两个数量级。所以,项目中关键位置代码,尽量避免使用反射。

反射在Go语言中是如何实现的?

我们前面的博文介绍过Go语言的接口,它是Go语言实现抽象的一个非常强大的工具。当向接口变量赋予一个实体类型的时候,接口会储存实体的类型信息,反射就是通过接口的类型信息实现的,反射建立在类型的基础上

Go语言在reflect包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。

type和interface

Go语言中,每一个变量都有一个静态类型,在编译阶段就已经确定,比如intfloat64[]int等。但这个类型是声明类型不是底层数据类型。

下面这个例子来自Go官方博客

代码语言:javascript
复制
type MyInt int

var i int
var j MyInt

变量ij的底层实现都是int型,但它们逻辑上是不同的静态类型(i的静态类型是int,但j的静态类型是MyInt),除非进行类型转换,否则两者不能使用等号做比较。

Go语言中反射主要和interface类型相关,interface类型底层实现如下,其中iface对应非空接口,eface对应空接口。

iface
代码语言:javascript
复制
type itab struct {
	inter *interfacetype
	_type *_type
	link *itab
	hash unit32
	bad bool
	inhasn bool
	unused [2] byte
	fun [1] unintptr
}

type iface strcut {
	tab *itab
	data unsafe.Pointer
}
eface
代码语言:javascript
复制
type eface strcut {
	_type *_type
	data unsafe.Pointer
}

efaceiface相比,只维护了iface所有字段中的一个_type字段和data字段,表示空接口所承载的具体的实体类型,data是一个指针,指向具体的值。

获取到_typedata的内容,就实现了反射。

反射的基本函数

reflect包中定义了一个接口和一个结构体,即reflect.Typereflect.Value,两者提供很多函数来获取存储在接口里的类型信息。

reflect.Type主要提供关于类型相关的信息,所以它和_type关联比较紧密

reflect.Value则结合_typedata两者,因此程序员可以通过它获取甚至改变类型的值。

reflect包中提供了两个基本的关于反射的函数来获取上述的接口和结构体,分别是TypeOfValueOf,它们的功能如其名,获取type信息和value信息:

代码语言:javascript
复制
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value

反射的三大定律

  1. Reflection goes from interface value to the reflection object.
  2. Reflection goes from reflection object to interface value.
  3. To modify a reflection object, the value must be settable.

直译如下:

  1. 反射发生在接口值和反射对象之间。
  2. 反射发生在反射对象和接口值之间。
  3. 反射对象要想能够更改,它的值必须是能够设置的。

前两者表达了对称性,后两者则是反射能实现的一种条件。

第一条:反射可以检测interface的一个具体值(对象)中的类型和值,通过前述TypeOfValueOf得到。

第二条:将类型和值封装成一个inteface的一个具体值(对选哪个)

第三条:对反射对象值的修改应当能作用到原值。

第三条举一个具体的例子,以帮助理解。

代码语言:javascript
复制
package main

import (
	"fmt"
	"reflect"
)

func main() {
	i := 1
	v := reflect.ValueOf(i)
	v.SetInt(2)
	fmt.Println(i)
}

运行如上代码,会panic报错如下,因为传参数进入ValueOf时是复制的i的一份值,并不是地址值:

代码语言:javascript
复制
panic: reflect: reflect.Value.SetInt using unaddressable value

goroutine 1 [running]:
reflect.flag.mustBeAssignableSlow(0x82)
	D:/Go/src/reflect/value.go:260 +0x146
reflect.flag.mustBeAssignable(...)
	D:/Go/src/reflect/value.go:247
reflect.Value.SetInt(0xb30920, 0xbdeb68, 0x82, 0x2)
	D:/Go/src/reflect/value.go:1633 +0x45
main.main()
	D:/GoProject/main/test.go:11 +0xc5

Process finished with exit code 2
代码语言:javascript
复制
package main

import (
	"fmt"
	"reflect"
)

func main() {
	i := 1
	v := reflect.ValueOf(&i)
	v.SetInt(2)
	fmt.Println(i)
}

传地址还不对,报错还是一样,这是因为v并代表是iv.Elem()才真正代表i

代码语言:javascript
复制
package main

import (
	"fmt"
	"reflect"
)

func main() {
	i := 1
	v := reflect.ValueOf(&i)
	v1 := v.Elem()
	v1.SetInt(2)
	fmt.Println(i)
	fmt.Println("type of v:", v.Type())
	fmt.Println("Settability of v", v.CanSet())
	fmt.Println("type of v:", v1.Type())
	fmt.Println("Settability of v", v1.CanSet())
}

运行结果:

代码语言:javascript
复制
2
type of v: *int
Settability of v false
type of v: int
Settability of v true

反射的实际应用

IDE的代码自动补全,对象序列化(json函数库),fmt相关函数的实现,ORM(对象关系应谁的实现)等等。

参考文献

深度解密Go语言之反射

Go程序设计语言-机械工业出版社

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/01/10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么要使用反射?使用反射有哪些缺点?
  • 反射在Go语言中是如何实现的?
    • type和interface
      • iface
      • eface
  • 反射的基本函数
  • 反射的三大定律
  • 反射的实际应用
  • 参考文献
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档