反射在Go语言中是通过reflect
包实现的。它允许程序在运行时检查对象的类型和值,甚至修改它们。Go语言中的每一个变量都包含两部分信息:类型(type)和值(value)。reflect
包让我们能够在运行时获取这些信息。
reflect.TypeOf()
:获取任何值的类型。reflect.ValueOf()
:获取任何值的运行时表示。使用反射的一个主要场景是处理动态数据结构,例如解析JSON或处理数据库查询结果。它也常用于编写通用的函数和包,这些函数和包可以处理各种类型的值,而不仅仅是特定的类型。
假设有一个结构体Person
,我们想动态地获取和修改其字段值。
package main
import (
"fmt"
"reflect"
)
// 反射
func main() {
a := 88.08
fmt.Println("a type:", reflect.TypeOf(a)) // a的类型为float64
fmt.Println("a name:", reflect.TypeOf(a).Name()) // a的类型名称为float64
fmt.Println("a bits:", reflect.TypeOf(a).Bits()) // a的位数为64
fmt.Println("--------------------------------")
fmt.Println("a value:", reflect.ValueOf(a)) // a的值为88.08
fmt.Println("a value:", &a) // a的地址为0xc0000a6058
fmt.Println("----------------reflect person-----------------")
person := Person{Name: "Jany", Age: 18}
reflectPerson(&person)
fmt.Println("----------------reflect int-----------------")
num := 100
reflectInt(num)
}
func reflectPerson(i interface{}) {
value := reflect.ValueOf(i).Elem()// 返回指针指向的值
fmt.Println(value) //{Jany 18} // 获取传入参数i的反射值
fmt.Println("type:", value.Type()) // type: main.Person // 获取value的类型
for i := 0; i < value.NumField(); i++ {
field := value.Field(i)
fmt.Println("field type:", field.Type(), field) // 字段的类型和字段值 // 遍历value的所有字段,并打印字段的类型和字段值
}
// 修改字段值
if nameField := value.FieldByName("Name"); nameField.IsValid() && nameField.CanSet() {
nameField.SetString("Bob")
}
fmt.Println("Modified:", i.(*Person)) // &{Bob 18} // 打印修改后的参数i的值
}
func reflectInt(i interface{}) {
rType := reflect.TypeOf(i)
fmt.Println("i rType=", rType)
rVal := reflect.ValueOf(i) //i rType= int
fmt.Printf("rVal:%v,rVal Type:%T\n", rVal, rVal) //rVal:100,rVal Type:reflect.Value
a := 10
b := int64(a) + rVal.Int() // 同类型做运算
fmt.Println("a+rVal=", b) //110
// 将i变为原来类型
c := rVal.Interface()
num := c.(int) // 只有接口才能断言
fmt.Println("num:", num) // 100
}
type Person struct {
Name string
Age int
}
在Go语言中,reflect
包的Elem
方法是用于处理指针和接口类型的反射对象的一个重要方法。这个方法的核心作用是获取一个指针所指向的元素的反射对象,或者是一个接口所持有的值的反射对象。
Elem
方法的基本概念当使用reflect.ValueOf
获取一个变量的反射值时,如果这个变量是一个指针或者接口,将得到的反射对象并不直接代表指针指向的值或接口的动态值。在这种情况下,Elem
方法就派上用场了。
Elem
返回该指针所指向的实际元素的reflect.Value
。Elem
返回接口实际持有的对象的reflect.Value
。假设有如下的结构体和函数:
type Person struct {
Name string
Age int
}
func PrintStructFields(i interface{}) {
val := reflect.ValueOf(i)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
// 假设 i 是一个结构体
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
fmt.Println(field.Type(), field)
}
}
在这个例子中,PrintStructFields
函数接受任意类型的参数。使用reflect.ValueOf
获取i
的反射值。如果i
是一个指针,调用Elem
来获取它所指向的实际元素。
这样,无论传递给PrintStructFields
的是一个结构体还是一个指向结构体的指针,函数都能正确处理并打印出结构体字段的信息。
Elem
之前,最好检查反射值的类型是否为指针或接口,这可以通过reflect.Value
的Kind
方法完成。Elem
被调用在一个非指针或非接口的reflect.Value
上,它会引发panic。因此,安全的做法是先检查Kind
。nil
,因为在nil
指针上调用Elem
也会引发panic。package main
import (
"fmt"
"reflect"
)
type Employee struct {
Name string `json:"name"`
Age int `json:"age"`
Sex string
Salary float64 `json:"salary"`
}
func (em Employee) Print() {
fmt.Println("-----------开始打印-----------")
fmt.Println(em)
fmt.Println("-----------结束打印-----------")
}
func (em Employee) Set(name string, age int, sex string, salary float64) Employee {
em.Name = name
em.Sex = sex
em.Salary = salary
em.Age = age
return em
}
func (em Employee) GetSum(a, b int) int {
return a + b
}
func reflectEmployee(i interface{}) {
tp := reflect.TypeOf(i)
val := reflect.ValueOf(i)
kd := val.Kind() //kd:struct
fmt.Printf("tp:%v, val:%v kd:%v\n", tp, val, kd)
if kd != reflect.Struct {
fmt.Println("expect struct!!!!!!!!!!!")
return
}
// 获取字段数量
numField := val.NumField()
fmt.Println("numField:", numField) //4
// 遍历结构体
for i := 0; i < numField; i++ {
fmt.Printf("field%d 值为%v\n", i, val.Field(i))
// 获取struct标签 需要用reflect.Type来获取标签
tagVal := tp.Field(i).Tag.Get("json") // 获取键值对的值
if tagVal != "" {
fmt.Printf("field%d tag为%v\n", i, tagVal)
}
}
// 获取结构体有多少个方法
numMethod := val.NumMethod()
fmt.Println("numMethod:", numMethod) //3
// 调用第二个方法 这里会调用Print方法 应为底层排序是按照方法名的ASCII排序的 GPS--012
val.Method(1).Call(nil)
// 调用第一个方法
var params []reflect.Value
params = append(params, reflect.ValueOf(99))
params = append(params, reflect.ValueOf(1))
res := val.Method(0).Call(params) // 参数接收一个reflect.Value类型切片
//fmt.Printf("type:%T,res=%v\n", res, res) //type:[]reflect.Value,res=[]
fmt.Printf("type:%T,res=%v\n", res, res[0].Int()) // 返回的结果是[]reflect.Value
// 调用第三个方法
var params2 []reflect.Value
params2 = append(params2, reflect.ValueOf("jojo"))
params2 = append(params2, reflect.ValueOf(18))
params2 = append(params2, reflect.ValueOf("男"))
params2 = append(params2, reflect.ValueOf(9999.99))
call := val.Method(2).Call(params2)
fmt.Println("call:", call[0]) //call: {jojo 18 男 9999.99}
}
func main() {
em := Employee{Name: "张三", Age: 22, Sex: "男", Salary: 11000.5}
//em.Print()
reflectEmployee(em)
}