什么是反射:
维基百科上的定义:在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。
《Go 语言圣经》中是这样定义反射的:Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。
不用反射不行吗:
实际上,它的本质是程序在运行期探知对象的类型信息和内存结构,不用反射能行吗?可以的!使用汇编语言,直接和内层打交道,什么信息不能获取?但是,当编程迁移到高级语言上来之后,像java/python/go,就不行了!就只能通过反射来达到此项技能。
为什么要用反射:
1、有时需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了,在程序运行中去动态识别这些参数类型。
2、获取结构体的Tag信息等。
3、显示一个类型的方法集。
4、....
不使用反射的理由:
- 与反射相关的代码,经常是难以阅读的。
- Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。
- 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。
go语言如何使用反射:
go语言反射里最重要的两个概念是Type和Value。
Type用于获取类型相关的信息(比如Slice的长度,struct的成员,函数的参数个数)
Value用于获取和修改原始数据的值(比如修改slice和map中的元素,修改struct的成员变量)
1、通过TypeOf()得到Type类型:
获取struct成员变量的信息
typeUser := reflect.TypeOf(common.User{}) //需要用struct的Type,不能用指针的Type
fieldNum := typeUser.NumField() //成员变量的个数
for i := 0; i < fieldNum; i++ {
field := typeUser.Field(i)
fmt.Printf("%d %s offset %d anonymous %t type %s exported %t json tag %s\n", i,
field.Name, //变量名称
field.Offset, //相对于结构体首地址的内存偏移量,string类型会占据16个字节
field.Anonymous, //是否为匿名成员
field.Type, //数据类型,reflect.Type类型
field.IsExported(), //包外是否可见(即是否以大写字母开头)
field.Tag.Get("json")) //获取成员变量后面``里面定义的tag
}
fmt.Println()
//可以通过FieldByName获取Field
if nameField, ok := typeUser.FieldByName("Name"); ok {
fmt.Printf("Name is exported %t\n", nameField.IsExported())
}
//也可以根据FieldByIndex获取Field
thirdField := typeUser.FieldByIndex([]int{2}) //参数是个slice,因为有struct嵌套的情况
fmt.Printf("third field name %s\n", thirdField.Name)
获取struct成员方法的信息、获取函数的信息、判断类型是否实现了某接口
# TODO
2、通过ValueOf()得到Value
userPtrValue := reflect.ValueOf(&common.User{
Id: 7,
Name: "杰克逊",
Weight: 65,
Height: 1.68,
})
fmt.Println(userPtrValue) //&{7 杰克逊 65 1.68}
//指针Value和非指针Value互相转换
userValue := userPtrValue.Elem() //Elem() 指针Value转为非指针Value
得到Value对应的原始数据
通过Interface()函数把Value转为interface{},再从interface{}强制类型转换,转为原始数据类型。或者在Value上直接调用Int()、String()等一步到位。
user := userValue.Interface().(common.User)
user2 := userPtrValue.Interface().(*common.User)
fmt.Println(user.Name)
fmt.Println(user2.Name)
通过Value修改原始数据的值
user := common.User{
Id: 7,
Name: "杰克逊",
Weight: 65.5,
Height: 1.68,
}
valueUser := reflect.ValueOf(&user) //由于go语言所有函数传的都是值,所以要想修改原来的值就需要传指
//由于valueI对应的原始对象是指针,通过Elem()返回指针指向的对象
valueUser.Elem().FieldByName("Weight").SetFloat(68.0) //FieldByName()通过Name返回类的成员变量