Golang中的反射,应该属于必知必会的内容。
什么是反射
Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。
为什么要用反射
- 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。
- 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。
反射的实现
静态类型&动态类型
Go 语言中,每个变量都有一个静态类型,在编译阶段就确定了,而运行时才知道变量类型的叫做动态类型
反射主要与Golang的interface类型相关,只有interface类型才有反射一说
var A interface{} // 静态类型interface{}
A = 10 // 静态类型为interface{} 动态为int
A = "String" // 静态类型为interface{} 动态为string
var M *int
A = M // A的值可以改变
在Go的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:(value, type),value是实际变量值,type是实际变量的类型。一个interface{}类型的变量包含了2个指针,一个指针指向值的类型(concrete type),另外一个指针指向实际的值(value)。
interface及其pair的存在,是Go实现反射的前提
反射就是用来检测存储在接口变量内部pair对的一种机制。
reflect包-实现反射
reflect 实现了运行时反射。reflect 包会帮助识别 interface{} 变量的底层具体类型和具体值。
1. reflect.Type 和 reflect.Value
reflect.Type 表示 interface{} 的具体类型,而 reflect.Value 表示它的具体值。
reflect.TypeOf() 和 reflect.ValueOf() 两个函数可以分别返回 reflect.Type 和 reflect.Value。
2. reflect.Kind
reflect.Type 变量 和 reflect.Value 变量都可以通过 Kind() 方法返回对应的接口变量的基础类型。
3. NumField()和Field()
NumField() 方法返回结构体中字段的数量,而 Field(i int) 方法结构体中第i个字段的reflect.Value。
type student struct {
id int
name string
age int
}
func main() {
stu := student{id: 1001, name: "小黄", age: 16}
value := reflect.ValueOf(stu)
fmt.Println("字段数:", value.NumField())
for i:=0;i<value.NumField();i++{
fmt.Printf("第 %d 个字段:%v \n", i+1, value.Field(i))
}
}
尽量避免用反射
- 与反射相关的代码,经常是难以阅读的
- Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错
- 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。
ValueOf(*ptr) 方法传递的参数必须是 指针类型 才可以修改字段否则会报错
Type 表示的是静态类型,而 kind 表示的是底层真实的类型
type MyInt int
reflect.TypeOf(MyInt).Kind()
reflect.Type 就是 MyInt,而非 int,如果想获得 int 只能使用Kind()
func main() {
type dog struct {
LegCount int
age int
}
// 获取dog实例地址的反射值对象
valueOfDog := reflect.ValueOf(&dog{})
// 取出dog实例地址的元素
valueOfDog = valueOfDog.Elem()
// 获取legCount字段的值
vLegCount := valueOfDog.FieldByName("LegCount")
vAge := valueOfDog.FieldByName("age")
// 尝试设置legCount的值
vLegCount.SetInt(4)
// 这里会报错
vAge.SetInt(4)
fmt.Println(vLegCount.Int())
}
对以下代码作了修改。这里只会打印出LegCount的值。将age改为Age即可修改,打印出值。
func main() {
type dog struct {
LegCount int
age int
}
// 获取dog实例地址的反射值对象
valueOfDog := reflect.ValueOf(&dog{})
// 取出dog实例地址的元素
valueOfDog = valueOfDog.Elem()
// 获取legCount字段的值
vLegCount := valueOfDog.FieldByName("LegCount")
vAge := valueOfDog.FieldByName("age")
// 尝试设置legCount的值
if vLegCount.CanSet() {
vLegCount.SetInt(4)
fmt.Println(vLegCount.Int())
}
// 这里会报错
if vAge.CanSet() {
vAge.SetInt(3)
fmt.Println(vAge.Int())
}
}
通过反射调用函数
package main
import (
"fmt"
"reflect"
)
// 普通函数
func add(a, b int) int {
return a + b
}
func main() {
// 将函数包装为反射值对象
funcValue := reflect.ValueOf(add)
// 构造函数参数, 传入两个整型值
paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}
// 反射调用函数
retList := funcValue.Call(paramList)
// 获取第一个返回值, 取整数值
fmt.Println(retList[0].Int())
}
通过反射调用对象中的方法
方法 | 描述 |
---|---|
Method(i int) Value | 根据索引,返回索引对应的方法 |
NumMethod() int | 返回结构体成员方法(包含私有) |
MethodByName(name string) Value | 根据给定字符串返回字符串对应的结构体方法 |
package main
import (
"fmt"
"reflect"
)
type Cat struct {
Name string
}
func (c *Cat) Sleep() {
fmt.Println("呜呜呜...")
}
func main() {
cat := Cat{}
valueOf := reflect.ValueOf(&cat)
showMethod := valueOf.MethodByName("Show") // 将Show改为Sleep可运行成功
showMethod.Call(nil)
}
当存在此名的方法时,返回地址。例如:0x8a9a60
当不存在此名的方法时,返回
反射实现:map 转 struct
func Map2Struct(m map[string]interface{}, obj interface{}) {
value := reflect.ValueOf(obj)
// obj 必须是指针且指针指向的必须是 struct
if value.Kind() == reflect.Ptr && value.Elem().Kind() == reflect.Struct {
value = value.Elem()
getMapName := func(key string) interface{} {
for k, v := range m {
if strings.EqualFold(k, key) {
return v
}
}
return nil
}
// 循环赋值
for i := 0; i < value.NumField(); i++ {
// 获取字段 type 对象
field := value.Field(i)
if !field.CanSet() {
continue
}
// 获取字段名称
fieldName := value.Type().Field(i).Name
fmt.Println("fieldName -> ", fieldName)
// 获取 map 中的对应的值
fieldVal := getMapName(fieldName)
if fieldVal != nil {
field.Set(reflect.ValueOf(fieldVal))
}
}
} else {
panic("must prt")
}
}
反射实现:struct 转 map
func Struct2Map(obj interface{}) map[string]interface{} {
value := reflect.ValueOf(obj)
if value.Kind() != reflect.Ptr || value.Elem().Kind() != reflect.Struct {
panic("must prt")
}
value = value.Elem()
t := value.Type()
// 创建 map
resultMap := make(map[string]interface{})
// 循环获取字段名称以及对应的值
for i := 0; i < value.NumField(); i++ {
val := value.Field(i)
typeName := t.Field(i)
if !val.CanSet() {
resultMap[typeName.Name] = reflect.New(typeName.Type).Elem().Interface()
continue
}
resultMap[typeName.Name] = val.Interface()
}
return resultMap
}
未完待续…