Go语言反射(reflect)及应用
Go语言反射(reflect)及应用
基本原理及应用场景
在编译时不知道类型的情况下,可更新变量、在运行时查看值、调用方法以及直接对它们的布局进行操作,这种机制被称为反射。
具体的应用场景大概如下:
- 动态地获取变量的各种信息(包括变量的类型
type
、类别kind
); - 如果是结构体变量,还可以获取结构体本身的字段、方法;
- 可以修改变量的值,调用变量的方法;
具体应用场景:
编写函数的适配器;
func funcName(funcPtr interface{},args ...interface{}){}
在暂时未知调用哪个接口的时候,进行传参,传入的是可变参数
args
,这时候配合传入的函数指针funcPtr
,利用反射,进行动态地调用函数。func testInt(b interface{}) {
//获取类型
rType := reflect.TypeOf(b)
fmt.Println("rType:",rType) //获取值
rVal := reflect.ValueOf(b)
n := rVal.Int()
fmt.Printf("rVal value:%v , type: %T\n",rVal,rVal)
fmt.Printf("n value: %v , type: %T \n",n,n) //获取interface{}
Ir := rVal.Interface()
fmt.Printf("Ir , value: %v , type: %T \n",Ir,Ir)
//类型断言
num := Ir.(int)
fmt.Printf("num , value: %v , type: %T \n",num,num)
} func testStruct(b interface{}) {
rType := reflect.TypeOf(b)
fmt.Println("rType:",rType) //获取值
rVal := reflect.ValueOf(b)
fmt.Printf("rVal value:%v , type: %T\n",rVal,rVal) //获取interface{}
Ir := rVal.Interface()
fmt.Printf("Ir , value: %v , type: %T \n",Ir,Ir) rKind := rVal.Kind() //表示数据类别
fmt.Printf("rkind , kind: %v , type: %T \n",rKind,rKind) //类型断言
num ,ok:= Ir.(Student)
if ok {
fmt.Printf("num , value: %v , type: %T \n", num, num)
fmt.Println(num.Name)
}
}
对结构体进行序列化,需要制定
Tag
。在对函数结构体序列化的时候,自定义
Tag
用到了反射,生成相对应的字符串。
reflect 包及相关常用函数
type Kind
type Kind uint
Kind代表Type类型值表示的具体分类。零值表示非法分类。
type Type
type Type interface {
...
}
Type类型用来表示一个go类型。
不是所有go类型的Type值都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知类型的分类。调用该分类不支持的方法会导致运行时的panic。
func TypeOf
func TypeOf(i interface{}) Type
TypeOf返回接口中保存的值的类型,TypeOf(nil)会返回nil。
type Value
type Value struct {
// 内含隐藏或非导出字段
}
Value为go值提供了反射接口。
不是所有go类型值的Value表示都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知该值的分类。调用该分类不支持的方法会导致运行时的panic。
Value类型的零值表示不持有某个值。零值的IsValid方法返回false,其Kind方法返回Invalid,而String方法返回"",所有其它方法都会panic。绝大多数函数和方法都永远不返回Value零值。如果某个函数/方法返回了非法的Value,它的文档必须显式的说明具体情况。
如果某个go类型值可以安全的用于多线程并发操作,它的Value表示也可以安全的用于并发。
func ValueOf
func ValueOf(i interface{}) Value
ValueOf返回一个初始化为i接口保管的具体值的Value,ValueOf(nil)返回Value零值。
func (Value) Kind
func (v Value) Kind() Kind
Kind返回v持有的值的分类,如果v是Value零值,返回值为Invalid
func (Value) Elem
func (v Value) Elem() Value
Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。
unc (Value) NumField
func (v Value) NumField() int
返回v持有的结构体类型值的字段数,如果v的Kind不是Struct会panic
func (Value) Field
func (v Value) Field(i int) Value
返回结构体的第i个字段(的Value封装)。如果v的Kind不是Struct或i出界会panic
func (Value) NumMethod
func (v Value) NumMethod() int
返回v持有值的方法集的方法数目。
func (Value) Method
func (v Value) Method(i int) Value
返回v持有值类型的第i个方法的已绑定(到v的持有值的)状态的函数形式的Value封装。返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。如果i出界,或者v的持有值是接口类型的零值(nil),会panic。
func (Value) MethodByName
func (v Value) MethodByName(name string) Value
返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装。返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。如果未找到该方法,会返回一个Value零值。
更多其它类型以及函数:Go语言标准库文档。
注意事项及细节
变量、
interface{}
和reflect.Value
是可以相互转换的。reflect.Value.Kind
,获取变量的类别,返回的是一个常量Type
和Kind
的区别Type
是类型,Kind
是类别,Type 和Kind
可能是相同的,也可能是不同的。比如:
var num int
= 10,num
的Type
是int
,Kind
也是int
;比如:
var stu Student stu
的Type
是packageXXX.Student
,Kind
是struct
。通过反射的来修改变量, 注意当使用
SetXxx
方法来设置,需要通过传入对应的指针类型来完成, 这样才能改变传入的变量的值;同时使用到
reflect.Value.Elem()
方法转换成对应保管的值的Value
封装,或者持有的指针指向的值的Value
封装。func testElem(b interface{}) {
rVal := reflect.ValueOf(b)
rVal.Elem().SetInt(20)//Elem()转换指针为所指向的值,相当于用一个变量引用该指针指向的值
}
/* func (Value) Elem
eg: func (v Value) Elem() Value
Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。
如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。*/
实例
需求:使用反射来遍历结构体的字段,调用结构体的方法,修改结构体字段的值,并获取结构体标签的值
package main
import (
"fmt"
"reflect"
)
//使用反射来遍历结构体的字段,调用结构体的方法,修改结构体字段的值,并获取结构体标签的值
//定义结构体
type Student struct {
Name string `json:"name"` // 是 ` ` (tab键上的~按键) ,不是 ' '
Sex string `json:"sex"`
Age int `json:"age"`
Sal float64 `json:"sal"`
}
func (s Student) GetName() string { //第0个方法
fmt.Println("该结构体Name字段值为:",s.Name)
return s.Name
}
func (s *Student) Set(newName string,newAge int,newSal float64){ //第2个方法
s.Name = newName
s.Age = newAge
s.Sal = newSal
s.Print()
}
func (s Student) Print() { //第1个方法
fmt.Println("调用 Print 函数输出结构体:",s)
}
//反射获取结构体字段、方法,并调用
func testReflect(b interface{}) {
rVal := reflect.ValueOf(b).Elem()
rType := reflect.TypeOf(b).Elem()
//判断是否是结构体在进行下一步操作
if rType.Kind() != reflect.Struct{
fmt.Println("该类型不是结构体。所以无法获取字段及其方法。")
}
//获取字段数量
numField := rVal.NumField()
fmt.Printf("该结构体有%d个字段\n",numField)
//遍历字段
for i := 0; i < numField; i++ {
//获取字段值、标签值
rFieldTag := rType.Field(i).Tag.Get("json")
if rFieldTag != "" {
fmt.Printf("结构体第 %v 个字段值为:%v ," +
"Tag‘json’名为:%v\n",i,rVal.Field(i),rFieldTag)
}
}
//获取方法数量
numMethod := rVal.NumMethod() //用指针可以获取到指针接收的方法
fmt.Printf("该结构体有%d个方法\n",numMethod)
//调用方法(方法顺序 按照ACSII码排序)
rVal.Method(0).Call(nil)
rVal.Method(1).Call(nil)
//参数也需要以 Value 的切片 传入
params := make([]reflect.Value ,3)
params[0] = reflect.ValueOf("hhhh")
params[1] = reflect.ValueOf(28)
params[2] = reflect.ValueOf(99.9)
rVal.Method(2).Call(params)
rVal.Method(1).Call(nil)
}
func main() {
stu := Student{
Name: "莉莉安",
Sex: "f",
Age: 19,
Sal: 98.5,
}
//调用编写的函数并输出
testReflect(&stu)
fmt.Println("主函数输出结构体 Student :",stu)
}
上面方法无法通过调用结构体中指针接收的方法,来修改结构体字段,无法获取指针接收的修改方法。
已解决,可选思路如下:
可通过直接获取字段值进行修改。(不够便捷)
用指针类型的
reflect.Value
可以获取到指针接收的方法(同时还包括值接受者的方法),不转换为指针所指向的值,直接用指针操作即可。可以识别并使用出指针接收的结构体的所有方法,包括值接收的、指针接收的方法。(前提是原结构体有修改方法)
func (Value) Elem()
Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。
注意:并不是地址,或者指向原值的引用。
结合解决思路,修改结果如下:
package main
import (
"fmt"
"reflect"
)
//使用反射来遍历结构体的字段,调用结构体的方法,修改结构体字段的值,并获取结构体标签的值
//定义结构体
type Student struct {
Name string `json:"name"` // 是 ` ` (tab键上的~按键) ,不是 ' '
Sex string `json:"sex"`
Age int `json:"age"`
Sal float64 `json:"sal"`
}
func (s Student) GetName() string { //第0个方法
fmt.Println("该结构体Name字段值为:",s.Name)
return s.Name
}
func (s *Student) Set(newName string,newAge int,newSal float64){ //第2个方法
s.Name = newName
s.Age = newAge
s.Sal = newSal
s.Print()
}
func (s Student) Print() { //第1个方法
fmt.Println("调用 Print 函数输出结构体:",s)
}
//反射获取结构体字段、方法,并调用
func testReflect(b interface{}) {
rVal := reflect.ValueOf(b).Elem()
rValI := reflect.ValueOf(b)
rType := reflect.TypeOf(b).Elem()
//判断是否是结构体在进行下一步操作
if rType.Kind() != reflect.Struct{
fmt.Println("该类型不是结构体。所以无法获取字段及其方法。")
}
//获取字段数量
numField := rVal.NumField()
fmt.Printf("该结构体有%d个字段\n",numField)
//遍历字段
for i := 0; i < numField; i++ {
//获取字段值、标签值
rFieldTag := rType.Field(i).Tag.Get("json")
if rFieldTag != "" {
fmt.Printf("结构体第 %v 个字段值为:%v ," +
"Tag‘json’名为:%v\n",i,rVal.Field(i),rFieldTag)
}
}
//获取方法数量
numMethod := rValI.NumMethod() //用指针可以获取到指针接收的方法
fmt.Printf("该结构体有%d个方法\n",numMethod)
//调用方法(方法顺序 按照ACSII码排序)
rVal.Method(0).Call(nil)
rVal.Method(1).Call(nil)
//参数也需要以 Value 的切片 传入
params := make([]reflect.Value ,3)
params[0] = reflect.ValueOf("hhhh")
params[1] = reflect.ValueOf(28)
params[2] = reflect.ValueOf(99.9)
rValI.Method(2).Call(params)
rVal.Method(1).Call(nil)
}
func main() {
stu := Student{
Name: "莉莉安",
Sex: "f",
Age: 19,
Sal: 98.5,
}
//调用编写的函数并输出
testReflect(&stu)
fmt.Println("主函数输出结构体 Student :",stu)
}
Go语言反射(reflect)及应用的更多相关文章
- Go语言反射reflect
目录 通过反射获取类型信息 理解反射的类型(Type)与种类(Kind) reflect.Elem() - 通过反射获取指针指向的元素类型 通过反射获取结构体的成员类型 通过反射获取值信息 使用反射值 ...
- go语言之行--接口(interface)、反射(reflect)详解
一.interface简介 interface(接口)是golang最重要的特性之一,Interface类型可以定义一组方法,但是这些不需要实现.并且interface不能包含任何变量. 简单的说: ...
- Go语言学习笔记(四)结构体struct & 接口Interface & 反射reflect
加 Golang学习 QQ群共同学习进步成家立业工作 ^-^ 群号:96933959 结构体struct struct 用来自定义复杂数据结构,可以包含多个字段(属性),可以嵌套: go中的struc ...
- Go语言反射规则
Go语言反射规则 - The Laws of Reflection 转:http://my.oschina.net/qbit/blog/213720 原文地址:http://blog.golang.o ...
- Go语言反射之类型反射
1 概述 类似于 Java,Go 语言也支持反射.支持反射的语言可以在运行时对程序进行访问和修改.反射的原理是在程序编译期将反射信息(如类型信息.结构体信息等)整合到程序中,并给提供给程序访问反射信息 ...
- Atitit.跨语言反射api 兼容性提升与增强 java c#。Net php js
Atitit.跨语言反射api 兼容性提升与增强 java c#.Net php js 1. 什么是反射1 1.1. 反射提供的主要功能:1 1.2. 实现反射的过程:1 ...
- Golang的反射reflect深入理解和示例
编程语言中反射的概念 在计算机科学领域,反射是指一类应用,它们能够自描述和自控制.也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examin ...
- 10. Go 语言反射
Go 语言反射 反射是指在程序运行期对程序本身进行访问和修改的能力.程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分.在运行程序时,程序无法获取自身的信息. 支持反射的语言可以在 ...
- Golang基础(5):Go语言反射规则
Go语言反射规则 - The Laws of Reflection 转:http://my.oschina.net/qbit/blog/213720 原文地址:http://blog.golang.o ...
随机推荐
- Azure 内容审查器之文本审查
内容审查器 Azure 内容审查器也是一项认知服务.它支持对文本.图形.视频进行内容审核.可以过滤出某些不健康的内容,关键词.使你的网站内容符合当地的法律法规,提供更好的用户体验. 文本内容审核 其中 ...
- Layman CSS3+H5实现上下垂直居中的几种主要方法
方法1:通过 translate 移位来实现 H5+CSS3: <div style="width: 100%; height: 100%; margin:0; padding: 0; ...
- 077 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 02 类和对象
077 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 02 类和对象 本文知识点:类和对象 说明:因为时间紧张,本人写博客过程中只是对知识点 ...
- HTML & CSS & JavaScript 从一个表格到一个灰阶颜色表 04
工具1:HBuilder X 1.9.9.20190522 工具2:火狐浏览器 67.0.4 (64 位) 目前,我们已经将一些行和列插入到表格中,并设置单元格的背景颜色,显示 RGB 值等. 例 7 ...
- 【题解】[CQOI]动态逆序对
题目链接 题意如题,维护一个动态序列的逆序对总数. 注意题目给的是\([1,n]\)的排列,所以没必要离散化了. 考虑逆序对:二维偏序可以用树状数组做,现在是三维偏序,即加了一个时间维度. 找一个数前 ...
- ASP。使用依赖注入的asp.net Core 2.0用户角色库动态菜单管理
下载source code - 2.2 MB 介绍 在开始这篇文章之前,请阅读我的前一篇文章: 开始使用ASP.NET Core 2.0身份和角色管理 在上一篇文章中,我们详细讨论了如何使用ASP.N ...
- 在阿里云上搭建私有GIT仓库
在阿里云上搭建私有GIT仓库 年轻人就得好好学习,不能这么颓废 最近做项目练练手,用到了github, 但是github访问速度是真的慢啊,下载项目,下载一天了.所以呢,我是个成熟的人了,只好自己搭建 ...
- C++调用全局函数与类成员函数
void testfunc(void *param) { printf("\n\tcall global function %s\n", param); } void *GetCl ...
- synchronized、volatile区别、synchronized锁粒度、模拟死锁场景、原子性与可见性
synchronized.volatile区别.synchronized锁粒度 synchronized synchronized是Java中的关键字,是一种同步锁.有以下几种用法: 用法 1.修饰方 ...
- 《罗辑思维》试读:U盘化生存
<罗辑思维>试读:U盘化生存 何为"U盘" 记得有一次我到一个大学去讲课,我随机做了一个调查.我说大四啦,咱们班同学谁找着工作了,一堆人举手.我又问都加入什么样的组织了 ...