反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
一、通过反射获取变量的值、变量的类型、变量的类别与将变量转换为interface类型
Go程序在运行期使用reflect包访问程序的反射信息,我们需要导入reflect包。
package main
import (
"reflect"
"fmt"
)
func main() {
var x float64 = 3.4 // 定义float类型的变量
// 1.通过 reflect.TypeOf() 反射获取变量的类型
fmt.Println("type:", reflect.TypeOf(x)) // 结果:type: float64
// 2.通过 reflect.ValueOf() 反射获取变量的值
fmt.Println("value:", reflect.ValueOf(x)) // 结果:value: 3.4
// 3.通过 reflect.ValueOf(x).Type() 反射已知变量x的值获取变量x的类型
fmt.Println("type:", reflect.ValueOf(x).Type()) // 结果:type: float64
// 4.通过 reflect.ValueOf(x).Kind() 反射已知变量x的值获取变量x的类别
fmt.Println("kind:", reflect.ValueOf(x).Kind()) // 结果:kind: float64
// 5.通过 reflect.ValueOf(x).Float() 反射已知变量x的值获取变量x的值(float64用Float64()方法获取值、string类型的变量用String()方法,如果int类型的变量用String()方法获取值会提示:<int Value>)
fmt.Println("value:", reflect.ValueOf(x).Float()) // 结果:value: 3.4
// 6.通过 reflect.ValueOf(x).Interface() 将已知变量x的类型转换为interface类型
fmt.Println(reflect.ValueOf(x).Interface()) // 结果:3.4(interface类型)
// 7.通过 reflect.ValueOf(x).Interface().(float64) 将interface类型的变量x转换为float64类型
y := reflect.ValueOf(x).Interface().(float64) // 转成float64类型
fmt.Println(y) // 结果:3.4
}
二、通过反射修改变量的值
package main
import (
"reflect"
"fmt"
)
func testInt(b interface{}) {
val := reflect.ValueOf(b) // 获取变量b的值(是个内存地址),结果为:0xc000010090
val.Elem().SetInt(20) //当传内存地址时,反射没有*val方法,反射提供了Elem()方法(效果和*val一样),通过.SetInt()设置值。
// 反射传进来的是地址时都要用到.Elem()方法(int类型需要用到.Int()方法来获取变量的值,float64类型用.Float64()方法来获取变量的值)
// 如果int类型用.Float64()来获取值时会出现提示:<int Value>
c := val.Elem().Int()
fmt.Printf("set data: %d\n", c) // 结果:set data: 20
}
func main() {
var b int = 12
testInt(&b) // 要传地址,传值会报错(传值反射只会修改变量的副本所以会报错)
fmt.Println(b) // 结果为:20
}
三、反射操作结构体
1、反射可以修改结构体的字段
2、反射可以执行结构体的方法
3、反射可以获取结构体的tag
package main
import (
"reflect"
"fmt"
)
// 定义结构体
type Student struct {
Name string `json:"name"`
Age int
Score float32
Sex string
}
// 给Student结构体定义两个方法
func (self Student) Print() {
fmt.Println("in Struct Print:", self)
}
func (self Student) Set(name string, age int, score float32, sex string) {
self.Name = name
self.Age = age
self.Score = score
self.Sex = sex
}
func testStruct(data *Student) {
tp := reflect.TypeOf(data) // 获取变量data的类型,结果为:*main.Student
val := reflect.ValueOf(data) // 获取变量data的值,结果为:&{zhangsan 18 88.3 男}
kd := val.Kind() // 获取变量data类别,指针类型的类别为:ptr
// 判断类别不是指针类型,并且指针的类别是struct,就抛出错误提示信息
if kd != reflect.Ptr && val.Elem().Kind() == reflect.Struct {
fmt.Println("expect struct")
return
}
num := val.Elem().NumField() // NumField() 获取结构体字段的数量
// 循环打印结构体字段
for i := 0; i < num; i++ {
fmt.Printf("bumfield - %d, %v, %v\n", i, val.Elem().Field(i), val.Elem().Field(i).Kind()) // i是下标,val.Field(i)是下标对应字段的值,val.Field(i).Kind()是字段的类型
/*
结果:
bumfield - 0, zhangsan, string
bumfield - 1, 18, int
bumfield - 2, 88.3, float32
bumfield - 3, 男, string
*/
}
// 修改结构体字段下标为0的值(指针类型都需要使用Elem()方法)
val.Elem().Field(0).SetString("Dream")
fmt.Println("修改后的name值:", val.Elem().Field(0)) // 结果为:修改后的name值: Dream
// 反射获取结构体字段下表为0的tag(指针类型都需要使用Elem()方法)
tagVal := tp.Elem().Field(0).Tag.Get("json")
fmt.Println("Struct Name tag is:", tagVal) // 结果为:Struct Name tag is: name
method := val.Elem().NumMethod() // NumMethod() 获取结构体方法的数量(指针类型都需要使用Elem()方法)
fmt.Printf("struct has %d method\n", method) // 结果:struct has 2 method
val.Elem().Method(0).Call(nil) // Method(0).Call(nil):调用struct下标为0的方法(即print方法,需要传一个空值)
}
func main() {
// 示例化一个Student结构体类型的变量a
var a Student = Student{
Name: "zhangsan",
Age: 18,
Score: 88.3,
Sex: "男",
}
// 利用反射操作结构体(要传地址,传值反射无法操作到变量本身)
testStruct(&a)
}