Go语言 反射

Go语言

14. 反射

反射的强大之处就在于它非常灵活,通常用于做一些通用框架代码,而不需要理解业务,因此具有快速处理不同业务的功能。但它强大的同时也带来了很多弊端,比如代码可读性和可维护性变差,性能也大打折扣。因此,是否使用反射需要进行利弊权衡后决定,并不是所有的程序都适合使用反射。

14.1 反射定义
14.1.1 反射的定义

Go语言提供了一种机制:在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,但是在编译时并不知道这些变量的具体类型。这种机制被称为反射。

Go语言在reflect包中实现了反射,通过reflect.TypeOf(),reflect.ValueOf()分别从类型、值的角度来描述一个Go对象。

反射并不是一个新概念,Go语言反射与其他语言有一定的差异,我们先来了解一下其他语言中的反射。

14.1.2 与其他语言的区别

Java的反射机制是其标志性的特征之一,正是这种语言本身支持的强大的机制,使很多流行的框架有了用武之地。

在运行状态中,Java对于任何的类,都能够确认这个类的所有方法和属性;对于任何一个对象,都能调用它的任意方法和属性。这种动态获取或者调用的方式就是Java的反射机制。

在Java中,通过反射机制在运行时能够做到:

  • 确定对象的类
  • 确定类的所有成员变量和方法
  • 动态调用任意一个对象的方法

Java可以通过三种方式反射获得一个Class对象,分别是通过对象调用getClass()方法、通过类名.class以及通过Class对象的forName()静态方法来获取。

在Go语言中,只能使用如上所示的第一种方法来反射获取运行状态的对象的值或方法,最重要的是,Go语言不支持通过对字符串解析,从而反射出对应的类型结构,这点与Java反射有着很大的区别。

package main

import (
   "fmt"
   "reflect"
)

func main() {
   var a interface{} = "我是字符串"
   typeOfa := reflect.TypeOf(a)

   fmt.Println("变量a的类型为:" + typeOfa.Name())
   valueOfa := reflect.ValueOf(a)
   if typeOfa.Kind() == reflect.String {
      fmt.Println("变量a的值为:" + valueOfa.String())
   }
}

使用reflect.TypeOf()获取了变量a的值类型,使用reflect.ValueOf()获取了变量a的原始值。

在这里插入图片描述

Go语言不支持解析string然后执行。Go语言的反射机制只能作用于已经存在的对象上。

14.2 基本用法

在使用反射时,我们会经常使用到反射的种类(Kind),reflect.Kind在Go语言reflect包中的定义如下:

type Kind uint
const (
    Invalid Kind = iota // 非法类型
    Bool // 布尔型
    Int // 有符号整型
    Int8 // 有符号8位整型
    Int16 // 有符号16位整型
    Int32 // 有符号32位整型
    Int64 // 有符号64位整型
    Uint // 无符号整型
    Uint8 // 无符号8位整型
    Uint16 // 无符号16位整型
    Uint32 // 无符号32位整型
    Uint64 // 无符号64位整型
    Uintptr // 指针
    Float32 // 单精度浮点数
    Float64 // 双精度浮点数
    Complex64 // 64位复数类型
    Complex128 // 128位复数类型
    Array // 数组
    Chan // 通道
    Func // 函数
    Interface // 接口
    Map // 映射
    Ptr // 指针
    Slice // 切片
    String // 字符串
    Struct // 结构体
    UnsafePointer // 底层指针
)

其中Map、Slice、Chan属于引用类型,使用起来类似于指针,但是在种类常量定义中仍然属于独立的种类,不属于Ptr。type A struct{}定义的结构体属于Struct种类,*A属于Ptr。

当我们使用反射获取到变量的类型时,就是用reflect.Kind来判断所属系统类型。

package main

import (
   "fmt"
   "reflect"
)

func main() {
   var a int
   typeOfA := reflect.TypeOf(a)

   if typeOfA.Kind() == reflect.Int {
      fmt.Printf("变量a的kind是int")
   } else {
      fmt.Printf("变量a的kind是int")
   }
}

在这里插入图片描述

14.2.1 获取类型信息

Go语言中使用reflect.TypeOf()来获取变量对象的类型信息,返回值的反射类型对象(reflect.Type)

var typeOfNum reflect.Type = reflect.TypeOf(num)

获取类型名称可以使用reflect.Type中的Name()方法;获取类型的种类Kind,可以使用对应的Kind()方法;对于指针类型变量,可以使用reflect.Type中的Elem()来反射获取指针指向的元素类型。

package main

import (
   "fmt"
   "reflect"
)

type Number int

type Person struct {
}

func checkType(t reflect.Type) {
   if t.Kind() == reflect.Ptr {
      fmt.Printf("变量的类型名称%v,指向的变量为:", t.Kind())
      t = t.Elem()
   }
   fmt.Printf("变量的类型名称=>%v,类型种类=> %v \n", t.Name(), t.Kind())
}

func main() {
   var num Number = 10
   typeOfNum := reflect.TypeOf(num)
   fmt.Println("typeOfNum")
   checkType(typeOfNum)

   var person Person
   typeOfPerson := reflect.TypeOf(person)
   fmt.Println("typeOfPerson")
   checkType(typeOfPerson)

   typeOfPersonPtr := reflect.TypeOf(&person)
   fmt.Println("typeOfPersonPtr")
   checkType(typeOfPersonPtr)
}

在这里插入图片描述

14.2.2 获取类型的值

Go语言使用reflect.TypeOf来获取类型信息,使用reflect.ValueOf来获取变量值的信息。reflect.ValueOf()函数可以获取值的反射值对象(reflect.Value)。

var valueOfNum reflect.Value = reflect.ValueOf(num)

reflect.Value是反射中非常重要的类型,获取变量的值和反射调用函数都需要使用该类型。reflect.Value有一系列的获取变量值的方法,例如Int()方法用来获取int类型变量的值,如果用此函数获取其他Kind(非Int、Int8、Int16、Int32、Int64类型变量)的值将会引发panic。

package main

import (
   "fmt"
   "reflect"
)

func checkValue(v reflect.Value) {
   if v.Kind() == reflect.Ptr {
      v = v.Elem()
   }
   if v.Kind() == reflect.Int {
      //方法一
      var v1 int = int(v.Int())
      //方法二
      var v2 int = v.Interface().(int)
      fmt.Println(v1, v2)
   }
}

func main() {
   var num int = 10
   valueOfNum := reflect.ValueOf(num)
   fmt.Println("valueOfNum")
   checkValue(valueOfNum)

   valueOfNumPtr := reflect.ValueOf(&num)
   fmt.Println("valueOfNumPtr")
   checkValue(valueOfNumPtr)
}

在这里插入图片描述

使用int类型变量为例子,函数checkValue()依旧会对指针类型变量做特殊处理,使用两个方法获取变量num的值,一个是使用上面介绍的Int()函数获取,再从reflect.Value类型强制转换成int类型数据,另一个是使用Interface()方法转换成Interface{}类型,再从Interface{}类型转换成int类型数据。

14.2.3 使用反射调用函数

使用反射调用函数需要用到reflect.ValueOf()方法传入想要反射的函数名,获取到reflect.Value对象,再通过reflect.Value对象的Call方法调用该函数。

func (v Value) Call(in []Value) []Value

Call方法使用输入的参数in调用v持有的函数。例如,如果len(in) == 3,v.Call(in)代表调用v(in[0], in[1], in[2])(其中Value值表示其持有值)。如果v的Kind不是Func会引发panic。它返回函数所有输
出结果的Value封装的切片。

package main

import (
   "fmt"
   "reflect"
)

func Equal(a, b int) bool {
   if a == b {
      return true
   }
   return false
}

func main() {
   //反射调用函数需要使用到valueOf
   valueOfFunc := reflect.ValueOf(Equal)
   //构造函数参数
   args := []reflect.Value{reflect.ValueOf(1), reflect.ValueOf(2)}

   //通过反射调用函数进行计算
   result := valueOfFunc.Call(args)
   fmt.Println("函数运行结果:", result[0].Bool())
}

在这里插入图片描述

Equal函数传入参数a、b判断两个值是否相等,返回对应结果。主函数使用reflect.ValueOf获取reflect.Value对象,并构造了参数args传入Call函数中.

14.3 对结构体的反射操作

反射不仅可以获取普通类型变量的值,还可以获取结构体成员的类型、成员变量的值以及调用结构体方法。

14.3.1 获取结构体成员类型

结构体通过reflect.TypeOf获取反射类型变量后,可以调用reflect.Type对象的NumField()方法获取结构体成员的数量,调用Field()则可以根据索引返回对应结构体字段的详细信息。

type StructField struct {
    Name string // 字段名
    PkgPath string // 字段路径
    Type Type // 字段反射类型对象
    Tag StructTag // 字段结构体标签
    Offset uintptr // 字段在结构体中的偏移
    Index []int // 字段的索引值
    Anonymous bool // 是否为匿名字段
}

reflect.Type中还可以使用FieldByName(name string)通过查找字段名来获取字段信息,以及使用FieldByIndex(index []int)通过索引值来获取字段信息。

package main

import (
   "fmt"
   "reflect"
)

type Person struct {
   Name string
   Age  int `json:"age"`
   string
}

func main() {
   person := Person{"小丁", 10, "备注"}
   typeOfPerson := reflect.TypeOf(person)

   //遍历所有结构体成员获取字段信息
   fmt.Println("遍历结构体:")
   for i := 0; i < typeOfPerson.NumField(); i++ {
      field := typeOfPerson.Field(i)
      fmt.Printf("字段名:%v 字段标签:%v 是否匿名字段:%v \n", field.Name, field.Tag, field.Anonymous)
   }

   //通过字段名获取字段信息
   if field, ok := typeOfPerson.FieldByName("Age"); ok {
      fmt.Println("通过字段名")
      fmt.Printf("字段名:%v , 字段标签中json: %v \n", field.Name, field.Tag.Get("json"))
   }

   //通过下标获取字段信息
   field := typeOfPerson.FieldByIndex([]int{1})
   fmt.Println("通过下标:")
   fmt.Printf("字段名:%v , 字段标签:%v \n", field.Name, field.Tag)
}

在main函数中使用Field(i)遍历获取所有字段信息,其中包括字段名、字段标签以及是否为匿名字段。使用FieldByName()及FieldByIndex()函数获取Age字段的信息。

在这里插入图片描述

创建结构体时,字段可以只有类型,而没有字段名,这样的字段称为匿名字段(Anonymous Field)。

14.3.2 获取结构体成员字段的值

获取结构体成员的值,我们仍需要使用reflect.ValueOf()方法获取到reflect.Value对象,使用对象的NumField()方法可以获取结构体成员的数量,使用Field()则可以根据索引返回对应结构体字段的reflect.Value反射值类型。

package main

import (
   "fmt"
   "reflect"
)

type Person struct {
   Name string
   Age  int `json:"age"`
   string
}

func main() {
   person := Person{"小丁", 10, "备注"}
   valueOfPerson := reflect.ValueOf(person)

   fmt.Printf("person的字段数量:%v \n", valueOfPerson.NumField())

   //通过下标访问获取字段值
   fmt.Println("Field")
   field := valueOfPerson.Field(1)
   fmt.Printf("字段值:%v \n", field.Int())

   //通过字段名获取字段值
   field = valueOfPerson.FieldByName("Age")
   fmt.Println("FieldByName")
   fmt.Printf("字段值: %v \n", field.Interface())

   //通过下标索引获取字段值
   field = valueOfPerson.FieldByIndex([]int{0})
   fmt.Println("FieldByIndex")
   fmt.Printf("字段值:%v \n", field.Interface())
}

对Person结构体变量分别使用Field()、FieldByName()、FieldByIndex()三个方法来获取结构体成员的值类型对象,并打印输出变量的值。

在这里插入图片描述

14.3.3 反射执行结构体方法

对于结构体方法,需要使用reflect.ValueOf()获取reflect.Value对象,然后调用该对象的MethodByName(Name)函数,找到想要反射调用的方法,再通过Call函数进行反射调用。

package main

import (
   "fmt"
   "reflect"
)

type Person struct {
   Name string
   Age  int `json:"age"`
   string
}

func (p Person) GetName() {
   fmt.Println(p.Name)
}

func main() {
   person := Person{"凤凤", 22, "喵咪"}
   valueOfPerson := reflect.ValueOf(person)

   //根据名字获取方法
   f := valueOfPerson.MethodByName("GetName")
   //执行结构体方法
   f.Call([]reflect.Value{})
}

在这里插入图片描述

14.4 反射三定律
14.4.5 接口到反射类型的转换

这里的反射类型指reflect.Type和reflect.Value。反射的第一定律为:反射可以将接口类型变量转换为反射类型变量。

reflect.TypeOf的定义,通过该函数,将传入的interface{}类型的变量进行解析后返回reflect.Type类型

func TypeOf(i interface{}) Type

reflect.ValueOf则是将传入的interface{}类型的变量进行解析后返回reflect.Value类型

func ValueOf(i interface{}) Value
package main

import (
   "fmt"
   "reflect"
)

func main() {
   var a int = 5
   fmt.Printf("type:%T \n", reflect.TypeOf(a))
   fmt.Printf("value:%T \n", reflect.ValueOf(a))
}

在这里插入图片描述

14.4.2 反射到接口类型的转换

反射的第二定律为:反射可以将反射类型变量转换为接口类型。这主要使用了reflect.Value对
象的Interface()方法,可以将一个反射类型变量转换为接口变量。

package main

import (
   "fmt"
   "reflect"
)

func main() {
   var a int = 5
   valueOfA := reflect.ValueOf(a)
   fmt.Println(valueOfA.Interface())
}

在这里插入图片描述

14.4.3 修改反射类型对象

反射的第三定律为:想要使用反射来修改变量的值,其值必须是可写的(CanSet)。这个值必须满足两个条件:一是变量可以被寻址(CanAddr),二是变量是可导出的(结构体字段名首字母需大写)。

对于可以被寻址,需要使用reflect.ValueOf()方法对反射对象的地址获取其reflect.Value对象,再使用Elem()获取实例地址的元素,通过此方式获取的reflect.Value是可以被寻址的。

valueOfPerson := reflect.ValueOf(&person)

对于结构体而言,结构体字段必须是可导出的,不能为匿名字段,且字段名的首字母必须要大写。

package main

import (
   "fmt"
   "reflect"
)

type Person struct {
   Name string
   age  int `json:"age"`
   string
}

func main() {
   person := Person{"小丁", 22, "备注"}
   valueOfPerson := reflect.ValueOf(&person)
   typeOfPerson := reflect.TypeOf(&person)

   for i := 0; i < valueOfPerson.Elem().NumField(); i++ {
      fieldValue := valueOfPerson.Elem().Field(i)
      fieldType := typeOfPerson.Elem().Field(i)
      fmt.Printf("类型名: %v 可以寻址: %v 可以设置 :%v \n", fieldType.Name, fieldValue.CanAddr(), fieldValue.CanSet())
   }

   fmt.Println("修改前:", person)
   //必须满足可寻址和可导出两个条件才能修改变量值
   valueOfPerson.Elem().Field(0).SetString("xiaoding")

   fmt.Println("修改后:", person)
}

在这里插入图片描述

14.5 反射的性能

示例

package main

import (
   "reflect"
   "testing"
)

type data struct {
   N int
}

const AssignTimes = 100000000

func BenchmarkNativeAssign(t *testing.B) {
   v := data{N: 1}
   for i := 0; i < AssignTimes; i++ {
      v.N = 3
   }
}

func BenchmarkReflectAssign(t *testing.B) {
   v := data{N: 1}
   vv := reflect.ValueOf(&v).Elem()
   for i := 0; i < AssignTimes; i++ {
      vv.FieldByName("N").SetInt(3)
   }
}

func foo(v int) {
}

const CallTimes = 100000000

func BenchmarkNativeCall(t *testing.B) {
   for i := 0; i < CallTimes; i++ {
      foo(i)
   }
}

func BenchmarkReflectCall(t *testing.B) {
   v := reflect.ValueOf(foo)
   for i := 0; i < CallTimes; i++ {
      v.Call([]eflect.Value{reflect.ValueOf(2)})
   }
}

BenchmarkNativeAssign表示不使用反射进行赋值操作,BenchmarkReflectAssign表示使用反射进行赋值操作,BenchmarkNativeCall表示不使用反射进行函数调用,BenchmarkReflectCall表示使用常规方式调用函数。

在这里插入图片描述

→ 反射效率特别特别低

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ding Jiaxiong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值