• GO语言-反射reflect


    概述

            在计算机科学中,反射是指计算机程序在运行时可以访问、检测和修改它本身状态或行为的一种能力。反射实在Java出现后迅速流行起来的一种概念,通过反射可以获取丰富的类型信息,可以利用这些类型信息做比较灵活的工作。

            如果不用反射也是可以的,那我们就需要用到汇编语言,与计算机的内存打交道。但我们常用的都是高级语言,则需要通过反射来实现。不同的语言实现反射的机制也是不同的,而且有些语言也不支持反射。《Go语言圣经》中是这样定义反射的:

    Go语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法。但是在编译时并不直到这些变量的具体类型。这称为反射机制。

    什么情况需要反射?

    需要反射的2个常见场景:

    1. 不能明确函数传入的参数的类型,需要在运行时对传入的具体参数进行类型判断。
    2. 不确定接口调用哪些方法,需要在运行时根据传入参数确定。 

    为什么不建议用反射?

    反射是一把双刃剑,虽然它的功能很强大。

    1. 反射相关的代码,一般是难以阅读的。
    2. GO语言作为一门静态语言,编译器能在编码过程中提前发现一些类型错误。但是对于反射代码是无能为力的,所以包含反射相关的代码,往往运行一段时间以后才会发现错误。这往往会导致比较严重的后果。
    3. 反射对于性能影响是比较大的,比正常代码运行速度慢一到两个数量级。出于运行效率来讲,尽量避免使用反射。

    反射如何实现的?

    Go语言中我们学习过interface,他是Go语言实现抽象的一个非常强大的工具。当给接口变量赋予一个实体类型的时候,接口会存储实体类型的信息。反射就是通过接口的类型信息实现的,反射建立在类型的基础之上

    Go语言在reflect包中定义了各种类型,实现了反射的各种函数。通过它们可以在运行时检测类型的信息、改变类型的值。

    回顾两个知识点:

    go语言是静态类型语言。在代码编译的时候,所有类型都已经确定了。

    interface{}——空接口。空接口是可以代表任意类型的。

    Go语言的反射是建立在类型之上的。

    • Go语言中指定类型创建的变量叫做是静态类型,在创建变量的时候类型就已经确定
    • 反射主要与interface{}类型相关,它是具体类型,只有interface类型才有反射的说法。
    • Go语言中,变量包括(type、value)两部分,type又分为静态类型和具体类型。每个interface变量都有一个对应的pair,pair中记录了实际变量的(type、value)。interface{}包含两个指针,一个指针指向type,一个指针指向value。其中type是一个具体类型。
    • 如果将一个interface{}类型,赋值给另一个interface{},则实际pair的信息是不会变的。也就是说值和类型都不会改变。

    反射,本质上就是用来检测存储在interface{}中的pair的一种机制。pair中本质上就是存储的type和value。type和value也是反射包中最重要的两个类型。

    反射reflect的使用

    reflect的基本功能TypeOf和ValueOf

    reflect反射包提供了两种方法,reflect.TypeOf()和reflect.ValueOf()

    • reflect.TypeOf()用来动态获取输入参数接口中的值的类型,如果接口为空返回nil。实际就是获取interface{}的pair中的type。
    • reflect.ValueOf()用来获取输入参数中的数据的值,如果为空返回0。实际就是获取interface{}的pair中的value。

    所有的具体类型都可以看作是interface{}空接口的具体实现。

    1. var x interface{}
    2. var y string
    3. func main() {
    4. x = 11.11
    5. fmt.Println("type x:", reflect.TypeOf(x))
    6. fmt.Println("value x:", reflect.ValueOf(x))
    7. y = "liqi-test"
    8. fmt.Println("type y:", reflect.TypeOf(y))
    9. fmt.Println("value y:", reflect.ValueOf(y))
    10. }

    reflect.TypeOf()返回的类型是Type,reflect.ValueOf()返回的类型是Vlue。对于Type和Vlue下,都包含了大量的方法。

    反射获取结构体数据

    1. type Person struct {
    2. Name string
    3. Age int
    4. Sex string
    5. }
    6. func (p Person) Say(msg string) {
    7. fmt.Println("hello,", msg)
    8. }
    9. func (p Person) PrintInfo() {
    10. fmt.Printf("姓名: %s, 年龄: %d, 性别: %s\n", p.Name, p.Age, p.Sex)
    11. }
    12. func main() {
    13. p1 := Person{"xiaoming", 18, "男"}
    14. getMsg(p1)
    15. }
    16. func getMsg(msg interface{}) {
    17. getType := reflect.TypeOf(msg)
    18. fmt.Println("Type name is:", getType.Name())
    19. fmt.Println("Kind is:", getType.Kind())
    20. getValue := reflect.ValueOf(msg)
    21. fmt.Println("get all Fields:", getValue)
    22. //获取结构体字段
    23. fmt.Println("---获取结构体字段信息---")
    24. for i := 0; i < getType.NumField(); i++ {
    25. field := getType.Field(i)
    26. fieldValue := getValue.Field(i).Interface()
    27. fmt.Printf("结构体Field:%v, 值:%v, 值的类型: %T\n", field.Name, fieldValue, fieldValue)
    28. }
    29. //获取方法
    30. fmt.Println("---获取结构体方法信息---")
    31. for i := 0; i < getType.NumMethod(); i++ {
    32. getMethod := getType.Method(i)
    33. fmt.Printf("方法名称: %s, 方法类型: %s\n", getMethod.Name, getMethod.Type)
    34. }
    35. }

     定义了一个Person结构体,字段有Name、Age、Sex,还定义了结构体的两个方法。

    • reflect.TypeOf(xx).Name()        获取类型的名称
    • reflect.TypeOf(xx).Kind()        获取类型的种类
    • reflect.TypeOf(xx).NumField()        获取结构体字段的数量
    • reflect.ValueOf(xx).NumField()        获取结构体字段对应的值的数量
    • reflect.TypeOf(xx).Field(index)        按下标获取结构体字段
    • reflect.ValueOf(xx).Field(i).Interface()        按下标获取结构体内字段的值;如果不加.Interface()得到的类型是Vlue类型,interface()方法相当于将Vlue类型强制转换为字段本身类型。
    • reflect.TypeOf(xx).Method(index)         按下表获取结构体方法
    • reflect.TypeOf(xx).Method(index).Name()        方法的名称(函数名称)
    • reflect.TypeOf(xx).Method(index).Type()        方法的类型(函数类型) 

    反射设置实际变量的值

    需要修改实际变量的值,也是通过reflect.ValueOf(X)获取reflect.Value对象,然后进行变量值的修改。但需要注意的是reflect.ValueOf(X)中的X必须是一个指针对象,然后使用Elem().Set()进行更改,Set()传入的数值必须是Value类型。

    1. func main() {
    2. var num float64 = 11.11
    3. fmt.Println("num的数值:", num)
    4. numValue := reflect.ValueOf(&num)
    5. fmt.Println("num的类型:", numValue.Elem().Type())
    6. fmt.Println("num是否可以修改:", numValue.Elem().CanSet())
    7. SetNum := reflect.ValueOf(22.22)
    8. numValue.Elem().Set(SetNum)
    9. fmt.Println("修改后num的数值:", num)
    10. fmt.Println("---修改结构体对象数值---")
    11. p1 := &Person{"xiaoming", 18, "男"}
    12. fmt.Println("修改前的结构体:", *p1)
    13. personValue := reflect.ValueOf(p1)
    14. fmt.Println("结构体对象是否可以修改?:", personValue.Elem().CanSet())
    15. personValue.Elem().FieldByName("Name").SetString("xiaohong")
    16. personValue.Elem().FieldByName("Age").SetInt(20)
    17. personValue.Elem().FieldByName("Sex").SetString("女")
    18. fmt.Println("修改后的结构体:", *p1)
    19. }

     

     反射调用方法 

    1. type Person struct {
    2. Name string
    3. Age int
    4. Sex string
    5. }
    6. func (p Person) Say(msg string) {
    7. fmt.Println("hello,", msg)
    8. }
    9. func (p Person) PrintInfo() {
    10. fmt.Printf("姓名: %s, 年龄: %d, 性别: %s\n", p.Name, p.Age, p.Sex)
    11. }
    12. func main() {
    13. p1 := Person{"xiaoming", 18, "男"}
    14. value := reflect.ValueOf(p1)
    15. method1 := value.MethodByName("Say")
    16. method2 := value.MethodByName("PrintInfo")
    17. //调用方法
    18. args1 := []reflect.Value{reflect.ValueOf("反射执行有参数的方法")}
    19. method1.Call(args1) //有参数
    20. method2.Call(nil) //无参数
    21. }

    反射调用函数 

    函数的调用和方法类似。

    1. func fun1() {
    2. fmt.Println("反射调用无参函数")
    3. }
    4. func fun2(msg string) {
    5. fmt.Println("hello,", msg)
    6. }
    7. func fun3(i int, s string) (a int, b string) {
    8. a = i + 1
    9. b = s + ",从函数中return"
    10. return a, b
    11. }
    12. func main() {
    13. //反射调用无参数函数
    14. funValue1 := reflect.ValueOf(fun1)
    15. if funValue1.Kind() == reflect.Func {
    16. funValue1.Call(nil)
    17. }
    18. //反射调用有参数函数
    19. funValue2 := reflect.ValueOf(fun2)
    20. if funValue2.Kind() == reflect.Func {
    21. funValue2.Call([]reflect.Value{reflect.ValueOf("反射调用有参函数")})
    22. }
    23. //反射调用有返回值函数
    24. funValue3 := reflect.ValueOf(fun3)
    25. if funValue3.Kind() == reflect.Func {
    26. resultValue := funValue3.Call([]reflect.Value{reflect.ValueOf(10), reflect.ValueOf("函数有返回值")})
    27. fmt.Println("有返回值函数的返回值:", resultValue[0].Interface(), resultValue[1].Interface())
    28. }
    29. }

     

  • 相关阅读:
    Redis 集群模式
    自动化测试的十大优势
    Mybatis (2)
    剑指offer专项突击版第30天
    爆字段名中select * from users as a join users解释
    selenium点击多个xpath一样的元素
    【Redis】List类型和底层原理
    Day83:服务攻防-开发组件安全&Jackson&FastJson各版本&XStream&CVE环境复现
    37.cuBLAS开发指南中文版--cuBLAS中的Level-2函数her()
    判断文本元素是否出现省略号,根据文本长度来控制是否显示鼠标悬浮提示el-tooltip
  • 原文地址:https://blog.csdn.net/qq522044637/article/details/125484523