反射
有时我们需要写一个函数,这个函数有能力统一处理各种值类型,而这些类型可能无法共享同一个接口,也可能布局未知,也有可能这个类型在我们设计函数时还不存在,这个时候我们就可以用到反射
反射是指在程序运行期间对程序本身进行访问和修改的能力。正常情况 程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们
反射的功能
1、反射可以在程序运行期间动态的获取变量的各种信息,比如变量的类型 类别
2、如果是结构体,通过反射还可以获取结构体本身的信息,比如结构体的字段、结构体的方法、结构体的 tag。
3、通过反射,可以修改变量的值,可以调用关联的方法
Go语言中的变量是分为两部分的:
• 类型信息:预先定义好的元信息。
• 值信息:程序运行过程中可动态变化的。
在 GoLang 的反射机制中,任何接口值都由是一个具体类型 和具体类型的值两部分组成的。
在 GoLang 中,反射的相关功能由内置的 reflect 包提供,任意接口值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两部分组成,并 且 reflect 包 提 供 了 reflect.TypeOf 和 reflect.ValueOf 两个重要函数来获取任意对象的 Value 和 Type
reflect.TypeOf() 获取任意值类型对象
使用 reflect.TypeOf()函数可以接受任意 interface{}参数,可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息
**在反射中关于类型还划分为两种:**类型(Type)和种类(Kind)因为在 Go 语言中我们可以使用 type 关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,
当需要区分指针、结构体等大品种的类型时,就会用到种类( Kind**)**。 举个例子,我们定义了两个指针类型和两个结构体类型,通过反射查看它们的类型和种类。
Go 语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回空
go
import (
"fmt"
"reflect"
)
func reflectFn(x interface{}) {
// 反射获取任意变量类型
v := reflect.TypeOf(x)
name := v.Name() // 类型名称
kind := v.Kind() // 种类(底层类型)
fmt.Printf("类型是%v,类型名称是%v,种类是%v \n", v, name, kind)
}
// 自定义一个myInt类型
type myInt int
// Person结构体
type Person struct {
Name string
Age int
}
func main() {
a := 10
b := "s"
// 打印基本类型
reflectFn(a) // 类型是int,类型名称是int,种类是int
reflectFn(b) // 类型是string,类型名称是string,种类是string
// 打印自定义类型和结构体类型
var i myInt = 3
var p = Person{
"1",
2,
}
reflectFn(i) // 类型是main.myInt,类型名称是myInt,种类是int
reflectFn(p) // 类型是main.Person,类型名称是Person,种类是struct
// 打印指针类型
var f = 25
reflectFn(&f) // 类型是*int,类型名称是,种类是ptr
}
reflect.ValueOf() 获取原始值
reflect.ValueOf()返回的是 reflect.Value 类型,其中包含了原始值的值信息。reflect.Value 与原始值之间可以互相转换
通过反射获取原始值
go
func reflectValue(x interface{}) {
// 通过反射获取到值
v := reflect.ValueOf(x)
fmt.Printf("%v, %T \n", v, v) // 10, reflect.Value,获取到的类型是reflect.Value
fmt.Printf("%v,%T", v.Int(), v.Int()) // 10,int64,获取到原始值和类型,因为传入是数字,所以是v.Int()
}
func main() {
var i = 10
reflectValue(i)
go
func reflectValue(x interface{}) {
v := reflect.ValueOf(x)
kind := v.Kind() // 获取种类
// 判断底层种类
switch kind {
// 是int类型
case reflect.Int64:
fmt.Println(v.Int())
case reflect.Float64:
fmt.Println(v.Float())
case reflect.String:
fmt.Println(v.String())
}
}
通过反射设置变量的值
go
import (
"fmt"
"reflect"
)
func reflectValue(x interface{}) {
v := reflect.ValueOf(x)
// 如果传入的值是一个指针地址,那需要通过Elem().Kind()获取具体种类,Elem()返回 Type 对象所表示的指针指向的数据
// 如果只是v.kind,获取的到的是指针类型
kind := v.Elem().Kind() // int64
if kind == reflect.Int64 {
// int是SetInt string是SetString等
v.Elem().SetInt(3)
}
}
func main() {
var i = 10
// 值类型修改副本不会影响原始值,所以需要通过指针地址
reflectValue(&i)
fmt.Println(i) // 3
}
结构体反射
任意值通过 reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的 NumField()和 Field()方法获得结构体成员的详细信息
通过类型变量获取结构体的属性信息
go
import (
"fmt"
"reflect"
)
// studen结构体
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
Score int `json:"score"`
}
// 结构体方法 获取学生信息
func (s Student) GetInfo() string {
return fmt.Sprintf("姓名%v 年龄%v 成绩%v", s.Name, s.Age, s.Score)
}
// 结构体方法 修改学生信息
func (s *Student) SetInfo(name string, age, score int) {
s.Name = name
s.Age = age
s.Score = score
}
// 结构体方法 打印
func (s Student) PrintInfo() {
fmt.Println("print info")
}
func PrintStructField(s interface{}) {
// 获取类型对象
t := reflect.TypeOf(s)
/*
通过类型变量里面的Field获取结构体字段
*/
// 通过类型变量.Field(下标)可以获取结构体的字段对象
field0 := t.Field(0)
// 获取到结构体的Name字段对象
fmt.Println(field0) // {Name string json:"name" 0 [0] false}
fmt.Printf("%#v \n", field0) // reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0xe748c80), Tag:"json:\"name\"", Offset:0x0, Index:[]int{0}, Anonymous:false}
// 获取字段名称
fmt.Println(field0.Name) // Name
// 获取字段类型
fmt.Println(field0.Type) // string
// 获取字段tag - json类型
fmt.Println(field0.Tag.Get("json")) // name
/*
通过类型变量里面的FieldByName获取结构体字段
*/
// FieldByName返回两个值,一个是具体的值,一个是是否成功
field1, ok := t.FieldByName("Age")
if ok {
fmt.Println(field1.Name) // 字段名称:Age
fmt.Println(field1.Type) // 字段类型:int
fmt.Println(field1.Tag.Get("json")) // 字段的json类型的tag :age
}
/*
通过类型变量的NumField获取该结构体有多少个字段
*/
var count = t.NumField()
fmt.Println(count) // 3
}
func main() {
var student = Student{"小李", 15, 100}
PrintStructField(student)
}
通过值变量获取结构体值的值信息
go
func PrintStructField(s interface{}) {
/*
通过值变量获取结构体属性对应的值
*/
v := reflect.ValueOf(s)
// 方式一
fmt.Println(v.FieldByName("Name")) // 小李
fmt.Println(v.FieldByName("Age")) // 15
// 方式二 可以通过循环获取所有的属性值
fmt.Println(v.Field(2)) // 100
}
通过反射修改结构体的属性值
go
func ReflectSetValue(s interface{}) {
// 类型对象
t := reflect.TypeOf(s)
// 值对象
v := reflect.ValueOf(s)
// 判断传入的结构体是否是指针类型,因为非指针是值类型,不能修改值,必现使用指针类型
if t.Kind() != reflect.Ptr {
fmt.Println("传入的不是指针类型")
return
// 判断传入的指针是不是结构体指针
} else if t.Elem().Kind() != reflect.Struct {
fmt.Println("传入的不是结构体指针")
return
}
// 获取Name字段的指针值
name := v.Elem().FieldByName("Name")
// 修改属性值
name.SetString("小王")
}
func main() {
var student = Student{"小李", 15, 100}
// 如果传入一个值接收者,没有办法修改结构体的值,如果要修改对应值,需要传入指针接收者
ReflectSetValue(&student)
fmt.Println(student.Name) // 小王
}
通过类型变量获取结构体方法
go
// 打印结构体方法
func PrintStructFn(s interface{}) {
t := reflect.TypeOf(s)
// 通过类型变量的Method(下标)获取结构体的方法
method0 := t.Method(0) // 下标取的方法和结构体的顺序没有关系,和结构体的ASCII码有关系
fmt.Println(method0.Name) // 下标为0的方法名:GetInfo
fmt.Println(method0.Type) // func(main.Student) string
// 通过MethodByName 获取结构体方法
// 该方法两个返回值,一个是方法名,一个是是否有该方法的状态
method1, ok := t.MethodByName("PrintInfo")
if ok {
fmt.Println(method1.Name) // PrintInfo
fmt.Println(method1.Type) // func(main.Student)
}
// 获取结构体一共有几个方法
fmt.Println(t.NumMethod()) // 2 Student结构体定义了3个方法,有两个接收者类型是结构体,有一个接收者类型是指针接收者,如果s接收的是值接收者,只能统计值接收者的方法数量,如果是指针接收者,可以统计所有的方法的数量
}
func main() {
var student = Student{"小李", 15, 100}
PrintStructFn(student)
}
通过值变量执行结构体方法
go
func RunStructFn(s interface{}) {
v := reflect.ValueOf(s)
// 方法一 通过下标执行对应的方法,Call传递对应参数,nil代表没有参数
v.Method(1).Call(nil) // print info
// 方法二 通过MethodByName执行对应方法,返回值是一个切片
// 传入参数调用,Call方法传入的参数需要是一个reflect.Value类型的切片
var params []reflect.Value
params = append(params, reflect.ValueOf("小李"))
params = append(params, reflect.ValueOf(20))
params = append(params, reflect.ValueOf(95))
v.MethodByName("SetInfo").Call(params)
fmt.Println(v.MethodByName("GetInfo").Call(nil)) // [姓名小李 年龄20 成绩95]
}
func main() {
var student = Student{"小李", 15, 100}
// 如果传入一个值接收者,没有办法修改结构体的值,如果要修改对应值,需要传入指针接收者
RunStructFn(&student)
}