Go反射底层原理及数据结构解析

 更新时间:2022年06月08日 10:00:35   作者:​ 树獭叔叔   ​  
这篇文章主要介绍了Go反射底层原理及数据结构解析,反射的实现和interface的组成很相似,都是由“类型”和“数据值”构成,下面小编分享更多相关内容需要的小伙伴可以参考一下

1. 反射的引入与介绍

在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

需要反射的 2 个常见场景:

  • 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
  • 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。

Java中动态代理与AOP的实现,就要借助这种操作。而对于我们Go语言来说,也提供了反射的机制。

有了前面对于interface{}底层数据结构的了解,Go中的每个实例对象可以分为两快,类型信息与数值信息。而我们Go中提供的反射机制,能分别拿到这两块信息。

image.png

数据interface中保存有结构数据,拿到该数据对应的内存地址,然后把该数据转成interface,通过查看interface中的类型结构,就可以知道该数据的结构了,其实以上就是Go反射通俗的原理。

image.png

2. 反射的数据结构

type Type interface {
    // 所有的类型都可以调用下面这些函数
	// 此类型的变量对齐后所占用的字节数
	Align() int
	// 如果是 struct 的字段,对齐后占用的字节数
	FieldAlign() int
	// 返回类型方法集里的第 `i` (传入的参数)个方法
	Method(int) Method
	// 通过名称获取方法
	MethodByName(string) (Method, bool)
	// 获取类型方法集里导出的方法个数
	NumMethod() int
	// 类型名称
	Name() string
	// 返回类型所在的路径,如:encoding/base64
	PkgPath() string
	// 返回类型的大小,和 unsafe.Sizeof 功能类似
	Size() uintptr
	// 返回类型的字符串表示形式
	String() string
	// 返回类型的类型值
	Kind() Kind
	// 类型是否实现了接口 u
	Implements(u Type) bool
	// 是否可以赋值给 u
	AssignableTo(u Type) bool
	// 是否可以类型转换成 u
	ConvertibleTo(u Type) bool
	// 类型是否可以比较
	Comparable() bool
	// 下面这些函数只有特定类型可以调用
	// 如:Key, Elem 两个方法就只能是 Map 类型才能调用
	// 类型所占据的位数
	Bits() int
	// 返回通道的方向,只能是 chan 类型调用
	ChanDir() ChanDir
	// 返回类型是否是可变参数,只能是 func 类型调用
	// 比如 t 是类型 func(x int, y ... float64)
	// 那么 t.IsVariadic() == true
	IsVariadic() bool
	// 返回内部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用
	Elem() Type
	// 返回结构体类型的第 i 个字段,只能是结构体类型调用
	// 如果 i 超过了总字段数,就会 panic
	Field(i int) StructField
	// 返回嵌套的结构体的字段
	FieldByIndex(index []int) StructField
	// 通过字段名称获取字段
	FieldByName(name string) (StructField, bool)
	// FieldByNameFunc returns the struct field with a name
	// 返回名称符合 func 函数的字段
	FieldByNameFunc(match func(string) bool) (StructField, bool)
	// 获取函数类型的第 i 个参数的类型
	In(i int) Type
	// 返回 map 的 key 类型,只能由类型 map 调用
	Key() Type
	// 返回 Array 的长度,只能由类型 Array 调用
	Len() int
	// 返回类型字段的数量,只能由类型 Struct 调用
	NumField() int
	// 返回函数类型的输入参数个数
	NumIn() int
	// 返回函数类型的返回值个数
	NumOut() int
	// 返回函数类型的第 i 个值的类型
	Out(i int) Type
        // 返回类型结构体的相同部分
	common() *rtype
	// 返回类型结构体的不同部分
	uncommon() *uncommonType
}
type Value struct {
    typ *rtype
    ptr unsafe.Pointer
    flag
}

反射的实现和interface的组成很相似,都是由“类型”和“数据值”构成,但是值得注意的是:interface的“类型”和“数据值”是在“一起的”,而反射的“类型”和“数据值”是分开的。

TypeValue提供了非常多的方法:例如获取对象的属性列表、获取和修改某个属性的值、对象所属结构体的名字、对象的底层类型(underlying type)等等

Go中的反射,在使用中最核心的就两个函数:

  • reflect.TypeOf(x)
  • reflect.ValueOf(x)

这两个函数可以分别将给定的数据对象转化为以上的TypeValue。这两个都叫做反射对象

3. 如何通过反射对象来修改原数据对象的值?

在Go中,任何函数的参数都是值的拷贝,而非原数据。

反射函数reflect.ValueOf()也不例外。我们目前得到的反射对象,都是原对象的copy的反射对象,而非原对象本身,所以不可以修改到原对象.

那如何修改呢?

首先,在Go中要想让函数“有副作用“,传值必须传指针类型的。

var x float64 = 5.7
v := reflect.ValueOf(&x)

时还不行,因为这样反射对象对应的是原数据对象的指针类型,必须要拿到当前类型的值类型(*v)。 Go提供了另外一个方法Elem()

p := v.Elem()
fmt.Println(p.CanSet()) // true

p.SetFloat(6.6)
fmt.Println(x) // 6.6

经过以上操作,就可以修改原数据了,完整过程如下:

var x float64 = 5.7
v := reflect.ValueOf(&x)
p := v.Elem()
fmt.Println(p.CanSet()) // true
p.SetFloat(6.6)
fmt.Println(x) // 6.6

到此这篇关于Go反射底层原理及数据结构解析的文章就介绍到这了,更多相关 Go反射原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • spring-boot-maven-plugin 配置有啥用

    spring-boot-maven-plugin 配置有啥用

    这篇文章主要介绍了spring-boot-maven-plugin 配置是干啥的,这个是SpringBoot的Maven插件,主要用来打包的,通常打包成jar或者war文件,本文通过示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-08-08
  • Java 超详细带你掌握矩阵的运算

    Java 超详细带你掌握矩阵的运算

    在学习机器学习算法时,发现运用java 来实现有些算法代码时,会有很大困难,其中有一点就是 java 本身并没有矩阵运算的 api,所以进行要实现矩阵运算就尤其复杂,让我们一起了解矩阵的运算
    2022-03-03
  • Spring@Autowired与@Resource的区别有哪些

    Spring@Autowired与@Resource的区别有哪些

    这篇文章主要为大家详细介绍了@Autowired与@Resource的区别,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-02-02
  • Java 在PPT中添加文本和图片超链接的实现方法

    Java 在PPT中添加文本和图片超链接的实现方法

    这篇文章主要介绍了Java 在PPT中添加文本和图片超链接的实现方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • SpringBoot使用Redis缓存的实现方法

    SpringBoot使用Redis缓存的实现方法

    这篇文章主要介绍了SpringBoot使用Redis缓存的实现方法,需要的朋友可以参考下
    2018-02-02
  • spring boot中多线程开发的注意事项总结

    spring boot中多线程开发的注意事项总结

    spring boot 通过任务执行器 taskexecutor 来实现多线程和并发编程。下面这篇文章主要给大家介绍了关于spring boot中多线程开发的注意事项,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2018-09-09
  • JAVA内存空间相关知识汇总

    JAVA内存空间相关知识汇总

    这篇文章主要介绍了JAVA内存空间相关知识,文中介绍的非常详细,代码帮助大家更好的参考和学习,感兴趣的朋友可以了解下
    2020-06-06
  • springboot全局字符编码设置方式(解决乱码问题)

    springboot全局字符编码设置方式(解决乱码问题)

    这篇文章主要介绍了springboot全局字符编码设置方式(解决乱码问题),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • Spring Boot整合邮件发送与注意事项

    Spring Boot整合邮件发送与注意事项

    这篇文章主要给大家介绍了关于Spring Boot整合邮件发送与注意事项的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-07-07
  • Java实现文件上传保存

    Java实现文件上传保存

    这篇文章主要为大家详细介绍了Java实现文件上传保存,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06

最新评论