在学习go语言的过程当中不可避免的学习到反射的机制,相比于java和c,go语言对于反射要更加的适配反射。反射主要是处于自描述和自适应,那么就可以通过对反射的应用从而使自己的属性进行一个更改,那么可以对这个进行学习从而对内存有更高效的处理
在学习的过程中可以注意到在编写的过程当中变量名会和内存地址有一个相对稳定的一个关系,在编辑器没有对这一段进行使用的时候,程序中任何一段代码都无法获得这一部分的变量。那么通过对支持反射的语言就存在很大的优势,其可以将内部属性比如说字段名称,类型信息,结构体信息都存在一个文件当中,确保这个文件可以正常的使用,而且还可以对这个进行一定程度上的修改,而且这个修改不是在程序运行结束之后进行修改,而是可以实现在程序运行的中间进行更改,那么可以理解反射其实本质就是对运行时的变量的获取和传递
那么在go语言拥有反射机制的情况下,我们可以通过对运行的时候动态的调用对象的方法和属性,那么该如何使用反射呢?
go官方已经处理好了,在反射时可以使用reflect包进行反射,直接调用这个包即可,当然也可以自写reflect包(可能有大佬写过?)
但需要注意,反射的功能太过于强大,所以可以预见的存在很多可能会导致的问题,比如代码可读行的下降等,那么我们在能不使用的情况下尽量还是使用其他方法对反射进行代替
缺点
在强大的同时也过于虚弱,在很多情况下可以避免的panic异常在这里会变得更加常见,更不用说其他报错,而且也不清楚异常的爆出是什么时候,可能在代码运行很长时间之后突然出现的报错
而且在使用反射之后会更容易导致代码的效率变得底下
但瑕不掩瑜,即使反射存在很多的问题,仍然无法掩盖它其为强大的功能,那么下面可以开始学习反射的相关知识点
反射在前面已经说过主要使用的是reflect这个函数包当中的函数
在go语言的反射机制当中任何的接口都可以进行相关的转换为一个具体的类型和一个具体类型的值,那么这里可以将其理解成为type和value这两个部分,在反射中存在两个函数即reflect.Type和reflect.Value进行获取
那么可以通过一个例子进行了解
package main
import (
"fmt"
"reflect"
)
type student5 struct {
Name string
Age int
}
func reflecttest1(x interface{}) {
rs := reflect.ValueOf(x)
fmt.Println(rs)
}
func reflecttest2(x interface{}) {
re := reflect.TypeOf(x)
fmt.Println(re)
}
func main() {
var a int = 10
reflecttest1(a)
reflecttest2(a)
c := student5{
Name: "sca",
Age: 12,
}
reflecttest1(c)
reflecttest2(c)
}
最后可以得到运行结果,
10
int
{sca 12}
main.student5
当然也不止这两个进行处理,在处理之后可以尝试在类型type当中存在的kind和name 。.kind有一个很重要的特质,其可以返回所测试的底层类型。在反射中需要区分指针等非常规类型我们更经常使用的是kind。
这里可以再举一个例子
package main
import (
"fmt"
"reflect"
)
type myInt int64
//反射
func reflectType(x interface{}) {
t := reflect.TypeOf(x)
//打印变量名和底层类型
fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind())
}
//调用reflectType返回类型
func main() {
var a *float32 // 指针
var b myInt // 自定义类型
var c rune // 类型别名
reflectType(a) // type: kind:ptr
reflectType(b) // type:myInt kind:int64
reflectType(c) // type:int32 kind:int32
type person struct {
name string
age int
}
var d = person{
name: "ac",
age: 18,
}
type class struct{
student int
}
var e = class{88}
reflectType(d) // type:person kind:struct
reflectType(e) // type:class kind:struct
}
在这里运行结果如下
type: kind:ptr
type:myInt kind:int64
type:int32 kind:int32
type:person kind:struct
type:class kind:struct
即可以发现Name基本上都是获得该变量的直接命名而kind则是直接返回其类型名
注意rune 是int32类型的别名,
而翻阅reflect手册即可得到在这个底层当中存在的不同存在
相比于typeof的其中不同,valureof则会显得更加简单粗暴一些。 其内部在学习过程当中还没有出现其他的内部存在的其他数据获得函数,其只是获得数据的类型信息,其原始值和类型信息是可以相互转化的
举一个例子
package main
import (
"fmt"
"reflect"
)
type bearst struct {
Name string
bearst int
}
func refluct1(x interface{}) {
v := reflect.ValueOf(x)
k := v.Kind()
switch k {
case reflect.Int:
fmt.Printf("type is int ,value is %d\n", int(v.Int()))
case reflect.Float32:
fmt.Printf("type is float32 ,value is %f\n", float32(v.Float()))
case reflect.Float64:
fmt.Printf("type is float64 ,value is %f\n", float64(v.Float()))
case reflect.Bool:
fmt.Printf("type is bool ,value is %t\n", bool(v.Bool()))
default:
fmt.Printf("nil")
}
}
func main() {
var a int = 10
var b float64 = 2.32
var c bool = false
//ic := bearst {
// Name: "swde",
// bearst :12,
//}
refluct1(a)
refluct1(b)
refluct1(c)
d := reflect.ValueOf(0)
fmt.Printf("type c :%T\n", d)
refluct1(d)
}
观察结果可以得到
type is int ,value is 10
type is float64 ,value is 2.320000
type is bool ,value is false
type c :reflect.Value
nil
其value值可以得到其中的原始的变量值,但值得注意的是,这里的数字是无法进行加减乘除运算的,这里的类型并不是传统意义上的int等类型,而是reflect.value这个类型,那么这里需要强制转换才可以使其可以进行加减乘除
那么我们可以对其进行处理
package main
import (
"fmt"
"reflect"
)
func racefe1(x interface{}) {
v := reflect.ValueOf(x)
if v.Elem().Kind() == reflect.Int {
v.Elem().SetInt(2923)
}
}
func main() {
var a int = 12
racefe1(&a)
fmt.Printf("type is int , value is %d\n", a)
}
注意我们在这里使用了一个新的函数Elem,这个函数是非常重要的在更改数据的时候,但有一个问题他只接受一个地址,那么我们需要在传入的时候进行一个地址的传入
然后再通过set函数中对不同类型的处理办法既可以进行数据的更改
那么最后我们需要对结构体进行反射处理,相比于上面的常规的数据类型,结构体的数据会更加复杂,不过幸运的是结构体的反射处理并不像结构体和常规数据类型一样复杂。先举一个例子可以表现出来这个简单
package main
import (
"fmt"
"reflect"
)
type student1 struct {
Name string `json:"name"`
Score int `json:"score"`
}
func main() {
//初始化
stu1 := student1{
Name: "小马",
Score: 99,
}
//返回类型
t := reflect.TypeOf(stu1)
fmt.Printf("name:%s kind:%v\n", t.Name(), t.Kind())
// 通过for循环遍历结构体的所有字段信息
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("json"))
}
//通过字段名返回指定结构体的信息
if scoreField, ok := t.FieldByName("Score"); ok {
fmt.Printf("name:%s index:%d type:%v json tag:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"))
}
}
可以发现我们如果需要对结构体当中的每一个元素进行处理的时候可以通过for循环进行,其中的元素数量可以通过NumField进行获得,点开Field这个包可以看到
// Field returns a struct type's i'th field.
// It panics if the type's Kind is not Struct.
// It panics if i is not in the range [0, NumField()).
Field(i int) StructField
// Field returns the i'th struct field.
func (t *structType) Field(i int) (f StructField) {
if i < 0 || i >= len(t.fields) {
panic("reflect: Field index out of bounds")
}
p := &t.fields[i]
f.Type = toType(p.typ)
f.Name = p.name.name()
f.Anonymous = p.embedded()
if !p.name.isExported() {
f.PkgPath = t.pkgPath.name()
}
if tag := p.name.tag(); tag != "" {
f.Tag = StructTag(tag)
}
f.Offset = p.offset()
所以可以了解到
//字段返回结构类型的第i个字段。
//如果类型的Kind不是Struct,则会发生恐慌。
//如果i不在范围[0,NumField())内,它会恐慌。
这个是专门处理结构体的办法,当然下面的if 也可以得到对这个的处理,
其中的原理则更像是逐一判断匹配的元素
像这里的是查取字段名为Score的元素
那么结果应该与上面for循环得到的第二个元素相似
观察结果
name:student1 kind:struct
name:Name index:[0] type:string json tag:name
name:Score index:[1] type:int json tag:score
name:Score index:[1] type:int json tag:score
可以得知确实如此