go语言中反射的应用介绍和案例

发布时间:2024年01月19日

一.反射基础

反射在Go语言中是通过reflect包实现的。它允许程序在运行时检查对象的类型和值,甚至修改它们。Go语言中的每一个变量都包含两部分信息:类型(type)和值(value)。reflect包让我们能够在运行时获取这些信息。

1.关键函数

  • 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
}

四.Elem方法

在Go语言中,reflect包的Elem方法是用于处理指针和接口类型的反射对象的一个重要方法。这个方法的核心作用是获取一个指针所指向的元素的反射对象,或者是一个接口所持有的值的反射对象。

1.Elem方法的基本概念

当使用reflect.ValueOf获取一个变量的反射值时,如果这个变量是一个指针或者接口,将得到的反射对象并不直接代表指针指向的值或接口的动态值。在这种情况下,Elem方法就派上用场了。

  • 对于指针类型,Elem返回该指针所指向的实际元素的reflect.Value
  • 对于接口类型,Elem返回接口实际持有的对象的reflect.Value

2.示例

假设有如下的结构体和函数:

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的是一个结构体还是一个指向结构体的指针,函数都能正确处理并打印出结构体字段的信息。

3.使用注意

  • 在调用Elem之前,最好检查反射值的类型是否为指针或接口,这可以通过reflect.ValueKind方法完成。
  • 如果Elem被调用在一个非指针或非接口的reflect.Value上,它会引发panic。因此,安全的做法是先检查Kind
  • 当处理指针时,还应检查它是否为nil,因为在nil指针上调用Elem也会引发panic。

五.通过反射操作结构体的字段、方法、tag标签

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=[<int Value>]
    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)
}
文章来源:https://blog.csdn.net/qq_49195366/article/details/135700636
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。