开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情
今天来简单学习下Go的反射概念,反射是指在程序运行期间可以对程序本身进行访问和修改的能力
变量的概念
- 变量包含类型信息和值信息:例如
var name string = "hello world" - 类型信息:每一个变量都会有一个静态的原始信息,这个类型是在编译时就已经定义好的
- 值信息:在程序运行过程中会动态改变的值
go中的reflect包封装了反射相关的方法:
- reflect.TypeOf():静态获取类型信息,返回的是reflect.Type类型
- reflect.VauleOf():动态获取值信息,返回的是reflect.Value类型
反射获取interface类型、值信息
package main
import (
"fmt"
"reflect"
)
func reflect_type(a interface{}) {
t := reflect.TypeOf(a) //获取静态类型
v := reflect.ValueOf(a)//获取值
fmt.Println("值是: ",v.Float()) //调用Float()方法
fmt.Println("类型是:", t)
// kind()可以获取具体类型
k := t.Kind()
fmt.Println(k)
switch k {
case reflect.Float64:
fmt.Printf("a is float64\n")
case reflect.String:
fmt.Println("string")
}
}
func main() {
var x float64 = 2.6
reflect_type(x)
}
反射中关于类型的定义分为两种:类型(Type)和种类(Kind) 因为在Go语言中我们可以使用type关键字构造很多自定义类型,而种类(Kind)就是指底层的类型是不可以修改的,当需要区分指针、结构体、map等大类型时,就会用到种类(Kind),通过kind()获取种类
Go语言的反射中的数组、切片、Map、指针等类型的变量,它们的.Name()都是返回空
type myInt int64
func reflectType(x interface{}) {
t := reflect.TypeOf(x)
fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind())
}
func main() {
var a *float32 // 指针
var b myInt // 自定义类型
var c rune // 类型别名
var d map[string]string
reflectType(a)
reflectType(b)
reflectType(c)
reflectType(d)
}
使用reflect.Value的Interface()将反射还原成一个接口(接口信息包括类型和值)
var val interface{} = 100
var val_ int = reflect.ValueOf(val).Interface().(int)
fmt.Println(val_) //100
反射修改变量的值
想要通过反射修改变量的值,必须传递变量地址才能修改变量值,如果传递值拷贝就不会修改原来的值,反射中使用Elem()方法来获取指针对应的值
// 反射修改值
func reflect_set_value(a interface{}) {
v := reflect.ValueOf(a)
k := v.Kind() //获取类型
switch k {
case reflect.Float64:
// 反射修改值
v.SetFloat(6.9) //错误
fmt.Println("a is ", v.Float())
case reflect.Ptr:
// Elem()获取地址指向的值
v.Elem().SetFloat(7.9)
fmt.Println("case:", v.Elem().Float())
}
}
func main() {
var x float64 = 3.4
// 反射认为下面是指针类型,不是float类型
fmt.Println("before:", x)
reflect_set_value(&x)
fmt.Println("after:", x)
}
反射修改结构体
通过reflect.ValueOf()方法获取结构体指针对象,再使用FieldByName方法获取对应字段的值,再通过SetString来修改结构体的值,演示代码如下:
// 定义结构体
type User struct {
Id int `json:"id" db:"id2"'`
Name string `json:"name" db:"name2"'`
Age int `json:"age" db:"age2"'`
}
// 修改结构体值
func SetValue(u interface{}) {
v := reflect.ValueOf(u)
// 获取指针指向的元素
v = v.Elem()
// 取字段
f := v.FieldByName("Name")
if f.Kind() == reflect.String {
f.SetString("xiaoMing")
}
}
func main() {
u := User{1, "YYQQ", 20}
fmt.Println("before:", u)
SetValue(&u)
fmt.Println("after:", u)
//获取字段信息
v := reflect.TypeOf(u)
for i := 0; i < v.NumField(); i++ {
// 获取字段的值信息
tag := v.Field(i).Tag.Get("json")
name := v.Field(i).Name
types := v.Field(i).Type
index := v.Field(i).Index
fmt.Printf("name:%s index:%d type:%v json tag:%v\n",
name, index, types, tag)
}
}
不能滥用反射:
- 基于反射的代码是极其脆弱的,反射的错误只有在运行过程中,才会触发panic
- 大量使用反射的代码,会让其他人难以理解
- 反射的性能低,运行效率不高
总结
今天简单的学习了Go的反射知识,还有很多细节的用法可以查看官方文档,之后要开始着手学习框架方面的知识,对于刚入门go语言的我来说,还有许多地方需要学习,有错误的地方欢迎大家指出,共同进步!!