Go语言的反射
反射是语言里面是非常重要的一个特性,我们经常会看见这个词,但是对于反射没有一个很好的理解,主要是因为对于反射的使用场景不太熟悉。
一、理解变量的内在机制
1.类型信息,元信息,是预先定义好的,静态的。
2.值信息,程序进行过程中,动态变化的。
二、反射和空接口
1.空接口相当于一个容器,能接受任何东西。
2.那怎么判断空接口变量存储的是什么类型呢?之前有使用过类型断言,这只是一个比较基础的方法
3.如果想获取存储变量的类型信息和值信息就要使用反射机制,所以反射是什么? 反射就是动态的获取变量类型信息和值信息的机制。
三、怎么利用反射分析空接口里面的信息呢?
①首先利用的是GO语言里面的Reflect包
②利用包里的TypeOf方法可以获取变量的类型信息
func reflect_typeof(a interface{}) {
t := reflect.TypeOf(a)
fmt.Printf("type of a is:%v\n", t) k := t.Kind()
switch k {
case reflect.Int64:
fmt.Printf("a is int64\n")
case reflect.String:
fmt.Printf("a is string\n")
}
}
利用Kind() 可以获取t的类型,如代码所示,这里可以判断a是Int64还是string, 像下面一样使用:
func main() {
var x int64 = 3
reflect_example(x) var y string = "hello"
reflect_example(y)
}
打印结果:
type of a is:int64
a is int64
type of a is:string
a is string
③利用包里的ValueOf方法可以获取变量的值信息
func reflect_value(a interface{}) {
v := reflect.ValueOf(a)
k := v.Kind()
switch k {
case reflect.Int64:
fmt.Printf("a is Int64, store value is:%d\n", v.Int())
case reflect.String:
fmt.Printf("a is String, store value is:%s\n", v.String())
}
}
利用ValueOf方法可以得到变量的值信息,ValueOf返回的是一个Value结构体类型,有趣的是 可以使用 v.Type() 获取该变量的类型,和上面reflect.TypeOf() 获取的结果一样。
此外,因为值信息是动态的,所以我们不仅仅可以获取这个变量的类型,还能取得这个变量里面存储的值,利用 v.Int() 、 v.String() 等等就能取得值。如上面的main,调用此函数返回的结果:
a is Int64, store value is:3
a is String, store value is:hello
这里存在一个问题,如果传进去一个类型,使用了错误的解析,那么将会在运行的时候报错, 例如将 一个string类型强行的v.Int()。
既然值类型是动态的,能取到保存的值,同样可以设置值。在反射里面有很多set的方法,例如SetFloat、SetInt()、SetString()等可以帮助我们设置值。
① 下面的例子,我想把 x设置为 6.28,但是会报错!
func main() {
var x float64 = 3.14
v := reflect.ValueOf(x)
v.SetFloat(6.28)
fmt.Printf("After Set Value is %f", x)
}
错误结果:
panic: reflect: reflect.Value.SetFloat using unaddressable value
......
结果上说明是不可设置的,为什么呢? 因为我们的x是一个值类型,而值类型的传递是拷贝了一个副本,当 v := reflect.ValueOf(x) 函数通过传递一个 x 拷贝创建了 v,那么 v 的改变并不能更改原始的 x。要想 v 的更改能作用到 x,那就必须传递 x 的地址 v = reflect.ValueOf(&x)。修改程序如下:
func main() {
var x float64 = 3.14
v := reflect.ValueOf(&x)
v.SetFloat(6.28)
fmt.Printf("After Set Value is %f", x)
}
结果:依然报错!为什么传了地址还报错?因为&x是地址了,所以它的类型就变了,可以通过v.Type(),看下改变成了什么:
func main() {
var x float64 = 3.14
v := reflect.ValueOf(&x)
fmt.Printf("type of v is %v", v.Type()) //打印的结果是:type of v is *float64
}
由程序可以知道,这个返回的是一个指针类型的。所以上面SetFloat才会失败,那怎么做?
我们正常的赋值,如果是地址的话,例如下面:一般我们都会对*y进行赋值, *的意思就是往这个地址里面赋值。
var y *float64 = new(float64)
*y = 10.12
fmt.Printf("y = %v", *y)
同样的,我们在反射里面也可以取地址,需要通过 Elem() 方法进行取地址。再次修改程序
func main() {
var x float64 = 3.14
v := reflect.ValueOf(&x)
fmt.Printf("type of v is %v\n", v.Type())
v.Elem().SetFloat(6.28)
fmt.Printf("After set x is %v", x)
}
结果为:
type of v is *float64
After set x is 6.28
四、利用反射获取结构体里面的方法和调用。
1.获取结构体的字段
我们可以通过上面的方法判断一个变量是不是结构体。
可以通过 NumField() 获取所有结构体字段的数目、进而遍历,通过Field()方法获取字段的信息。
type Student struct {
Name string
Sex int
Age int
Score float32
} func main() {
//创建一个结构体变量
var s Student = Student{
Name: "BigOrange",
Sex: 1,
Age: 10,
Score: 80.1,
} v := reflect.ValueOf(s)
t := v.Type()
kind := t.Kind() //分析s变量的类型,如果是结构体类型,那么遍历所有的字段
switch kind {
case reflect.Int64:
fmt.Printf("s is int64\n")
case reflect.Float32:
fmt.Printf("s is int64\n")
case reflect.Struct:
fmt.Printf("s is struct\n")
fmt.Printf("field num of s is %d\n", v.NumField())
//NumFiled()获取字段数,v.Field(i)可以取得下标位置的字段信息,返回的是一个Value类型的值
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
//打印字段的名称、类型以及值
fmt.Printf("name:%s type:%v value:%v\n",
t.Field(i).Name, field.Type().Kind(), field.Interface())
}
default:
fmt.Printf("default\n")
}
}
执行结果:
s is struct
field num of s is 4
name:Name type:string value:BigOrange
name:Sex type:int value:1
name:Age type:int value:10
name:Score type:float32 value:80.1
这里需要说明几个问题:
①打印字段名称的时候,使用的是t.Field(i).Name ,Name是静态的,所以属于类型的信息
②打印值的时候,这里将field.Interface()实际上相当于ValueOf的反操作(可以参考这篇文章https://www.cnblogs.com/baicaiyoudu/archive/2016/09/25/5905766.html),所以才能把值打印出来
③此外如果Student中的Name字段变为name(私有),那么则会报错,不能反射出私有变量 错误信息 “panic: reflect.Value.Interface: cannot return value obtained from unexported field or method”
2.对结构体内的字段进行赋值操作
参考下面的代码,对上面的Student进行赋值操作:
func main() {
s := Student{
Name: "BigOrange",
Sex: 1,
Age: 10,
Score: 80.1,
} fmt.Printf("Name:%v, Sex:%v,Age:%v,Score:%v \n", s.Name, s.Sex, s.Age, s.Score)
v := reflect.ValueOf(&s) //这里传的是地址!!! v.Elem().Field(0).SetString("ChangeName")
v.Elem().FieldByName("Score").SetFloat(99.9) fmt.Printf("Name:%v, Sex:%v,Age:%v,Score:%v \n", s.Name, s.Sex, s.Age, s.Score)
}
结果:
Name:BigOrange, Sex:1,Age:10,Score:80.1
Name:ChangeName, Sex:1,Age:10,Score:99.9
3.获取结构体里面的方法
可以通过NumMethod()获得接头体里面的方法数量,然后遍历通过Method()获取方法的具体信息。如下代码所示:
//新增-设置名称方法
func (s *Student) SetName(name string) {
fmt.Printf("有参数方法 通过反射进行调用:%v\n", s)
s.Name = name
}
//新增-打印信息方法
func (s *Student) PrintStudent() {
fmt.Printf("无参数方法 通过反射进行调用:%v\n", s)
} func main() {
s := Student{
Name: "BigOrange",
Sex: 1,
Age: 10,
Score: 80.1,
} v := reflect.ValueOf(&s)
//取得Type信息
t := v.Type() fmt.Printf("struct student have %d methods\n", t.NumMethod()) for i := 0; i < t.NumMethod(); i++ {
method := t.Method(i)
fmt.Printf("struct %d method, name:%s type:%v\n", i, method.Name, method.Type)
}
}
输出:
struct student have 2 methods
struct 0 method, name:PrintStudent type:func(*main.Student)
struct 1 method, name:SetName type:func(*main.Student, string)
从结果中看到我们可以获取方法的名称以及签名信息,并且这个方法的输出顺序是按照字母排列的。
并且输出结果可以看到一个有趣的现象:结构体的方法其实也是通过函数实现的例如 func(s Student) SetName(name string) 这个方法,反射之后的结果就是 func(main.Student , string) 实际上把Student当参数了。
此外还可以通过反射来调用这些方法。想要通过反射调用结构体里面的方法,首先要知道方法调用时一个动态的,所以要先通过ValueOf获取值,然后通过获取的值进行方法的调用 ,通过 value里面的Method方法 返回一个方法,然后通过Call方法调用,Call是参数是一个切片,也就是参数的列表。以下是Call方法的定义:可以看到参数是一个Value的数组:
如下代码展示了如何调用有参数的方法和无参数的方法:
func main() {
s := Student{
Name: "BigOrange",
Sex: 1,
Age: 10,
Score: 80.1,
} v := reflect.ValueOf(&s) //通过reflect.Value获取对应的方法并调用
m1 := v.MethodByName("PrintStudent")
var args []reflect.Value
m1.Call(args) m2 := v.MethodByName("SetName")
var args2 []reflect.Value
name := "stu01"
nameVal := reflect.ValueOf(name)
args2 = append(args2, nameVal)
m2.Call(args2)
m1.Call(args)
}
执行结果:
无参数方法 通过反射进行调用:&main.Student{Name:"BigOrange", Sex:1, Age:10, Score:80.1}
有参数方法 通过反射进行调用:&main.Student{Name:"BigOrange", Sex:1, Age:10, Score:80.1}
无参数方法 通过反射进行调用:&main.Student{Name:"stu01", Sex:1, Age:10, Score:80.1}
上面格式打印:
%v 相应值的默认格式。 Printf("%v", people) {zhangsan},
%+v 打印结构体时,会添加字段名 Printf("%+v", people) {Name:zhangsan}
%#v 相应值的Go语法表示 Printf("#v", people) main.Human{Name:"zhangsan"}
五、怎么获取结构体里tag的信息。
有时候我们在类型上面定义一些tag,例如使用json和数据库的时候。Field()方法返回的StructField结构体中保存着Tag信息,并且Tag信息可以通过一个Get(Key)的方法获取出来,如下代码所示:
type Student struct {
Name string `json:"jsName" db:"dbName"`
} func main() {
s := Student{
Name: "BigOrange",
}
v := reflect.ValueOf(&s)
t := v.Type()
field0 := t.Elem().Field(0)
fmt.Printf("tag json=%s\n", field0.Tag.Get("json"))
fmt.Printf("tag db=%s\n", field0.Tag.Get("db"))
}
结果:
tag json=jsName
tag db=dbName
六、应用场景
1.序列化和反序列化,比如json, protobuf等各种数据协议
2.各种数据库的ORM,比如gorm,sqlx等数据库中间件
3.配置文件解析相关的库,比如yaml、ini等
Go语言的反射的更多相关文章
- go语言通过反射获取和设置结构体字段值的方法
本文实例讲述了go语言通过反射获取和设置结构体字段值的方法.分享给大家供大家参考.具体实现方法如下: type MyStruct struct { N int } n := MyStruct{ 1 } ...
- Go语言之反射(一)
反射 反射是指在程序运行期对程序本身进行访问和修改的能力.程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分.在运行程序时,程序无法获取自身的信息.支持反射的语言可以在程序编译期将 ...
- 深度解密Go语言之反射
目录 什么是反射 为什么要用反射 反射是如何实现的 types 和 interface 反射的基本函数 反射的三大定律 反射相关函数的使用 代码样例 未导出成员 反射的实际应用 json 序列化 De ...
- Go语言 8 反射
文章由作者马志国在博客园的原创,若转载请于明显处标记出处:http://www.cnblogs.com/mazg/ Go学习群:415660935 8.1概念和作用 Reflection(反射)在计算 ...
- Go语言之反射(二)
反射的值对象 反射不仅可以获取值的类型信息,还可以动态地获取或者设置变量的值.Go语言中使用reflect.Value获取和设置变量的值. 使用反射值对象包装任意值 Go语言中,使用reflect.V ...
- 列举java语言中反射的常用方法
package review;/*12:43 2019/7/21*/ import model.AnotherClass; import model.OneClassMore; import mode ...
- Go语言之反射(三)
结构体转JSON JSON格式是一种用途广泛的对象文本格式.在Go语言中,结构体可以通过系统提供的json.Marshal()函数进行序列化.为了演示怎么样通过反射获取结构体成员以及各种值的过程,下面 ...
- go语言之反射
---恢复内容开始--- 一 :并发基础 1 并发和并行 并发和并行是两个不同的概念: 并行意味着程序在任意时刻都是同时运行的: 并发意味着程序在单位时间内是同时运行的 详解: 并行就是在任一粒度的时 ...
- Go语言基础之反射
Go语言基础之反射 本文介绍了Go语言反射的意义和基本使用. 变量的内在机制 Go语言中的变量是分为两部分的: 类型信息:预先定义好的元信息. 值信息:程序运行过程中可动态变化的. 反射介绍 反射是指 ...
随机推荐
- hdu 2018 母牛的故事 动态规划入门题
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2018 设 f[i][j] 表示第i天年龄为j的母牛个数,其中j=4代表所有年龄达到4岁的成年母牛,则: ...
- 如何使用threejs实现第一人称视角的移动
在数据可视化领域利用webgl来创建三维场景或VR已经越来越普遍,各种开发框架也应运而生.今天我们就通过最基本的threejs来完成第一人称视角的场景巡检功能.如果你是一位threejs的初学者或正打 ...
- 关于反向生成url
1.模板渲染 <form action="{% url "bieming" 参数 %}" > <input type="text&q ...
- 说说Spring XML的头
部分内容截取自(http://blog.csdn.net/zhch152/article/details/8191377,http://iswift.iteye.com/blog/1657537) 在 ...
- docker 使用阿里云镜像加速
1.登录阿里云 2.进入控制台 3.搜索 “容器镜像服务” 下拉点击 “镜像加速器” 复制自己的私有地址 进入自己的docker宿主机器(替换下面的地址为自己的私有地址) 修改daemon配置文件/e ...
- GSVA的使用
GSVA的简介 Gene Set Variation Analysis,被称为基因集变异分析,是一种非参数的无监督分析方法,主要用来评估芯片核转录组的基因集富集结果.主要是通过将基因在不同样品间的表达 ...
- 开始使用 Manjaro(添加源+字体渲染去模糊+软件安装+优化配置+常见错误)(30)
1. 添加 archlinux 镜像源 1. 步骤一 向 /etc/pacman.d/mirrorlist 中添加国内镜像地址 1.1 方法1:自动添加 1. 输入如下命令查看国内镜像源,并按质量排序 ...
- python 之 前端开发(CSS三大特性、字体属性、文本属性、背景属性)
11.38 css三大特性 11.381 继承性 1.定义:给某一个元素设置一些属性,该元素的后代也可以使用,这个我们就称之为继承性2.注意: 1.只有以color.font-.text-.l ...
- 写Markdown博客时遇到的一些问题
成对的美元符号$,无法转义 相同的文本,就因为成对的$(美元符号),上面显示成了公式(Math)-而且还无法转义!下面用单行代码(``)-键盘"1"左侧的键,显示就正常了 下图方法 ...
- 用ASP.NET Web API技术开发HTTP接口(一)
开发工具 Visual Studio 2013 SQL Server 2008 R2 准备工作 启动Visual Studio 2013,新建一个ASP.NET Web应用程序,命名为SimpleAP ...