Go语言:运行时反射,深度解析!

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 反射有两种类型 reflect.Value 和 reflect.Type ,分别表示变量的值和类型,并且提供了两个函数 reflect.ValueOf 和 reflect.TypeOf 分别获取任意对象的 reflect.Value 和 reflect.Type。

反射


reflect.Value 和 reflect.Type

反射有两种类型 reflect.Valuereflect.Type ,分别表示变量的值和类型,并且提供了两个函数 reflect.ValueOfreflect.TypeOf 分别获取任意对象的 reflect.Valuereflect.Type

reflect.Value

reflect.Value 可以通过函数 reflect.ValueOf 获得。reflect.Value 被定义为一个 struct 结构体:

type Value struct {
   typ *rtype
   ptr unsafe.Pointer
   flag
}
复制代码

可以看到,这个结构体中的字段都是私有的,我们只能使用 reflect.Value 的方法,它的方法有:

//针对具体类型的系列方法
//以下是用于获取对应的值
Bool
Bytes
Complex
Float
Int
String
Uint
CanSet //是否可以修改对应的值
//以下是用于修改对应的值
Set
SetBool
SetBytes
SetComplex
SetFloat
SetInt
SetString
Elem //获取指针指向的值,一般用于修改对应的值
//以下Field系列方法用于获取struct类型中的字段
Field
FieldByIndex
FieldByName
FieldByNameFunc
Interface //获取对应的原始类型
IsNil //值是否为nil
IsZero //值是否是零值
Kind //获取对应的类型类别,比如Array、Slice、Map等
//获取对应的方法
Method
MethodByName
NumField //获取struct类型中字段的数量
NumMethod//类型上方法集的数量
Type//获取对应的reflect.Type
复制代码

方法分为三类:

  1. 获取和修改对应的值
  2. struct 类型的字段有关,用于获取对应的字段
  3. 类型上的方法集有关,用于获取对应的方法

任意类型的对象 与 reflect.Value 互转:

func main() {
   i := 5
   //int to reflect.Value
   iv:=reflect.ValueOf(i)
   //reflect.Value to int
   i1 := iv.Interface().(int)
   fmt.Println(i1)
}
复制代码

修改对应的值

func main() {
   i := 5
   ipv := reflect.ValueOf(&i)
   ipv.Elem().SetInt(6)
   fmt.Println(i)
}
复制代码
  • 示例中我们通过反射修改了一个变量
  • reflect.ValueOf 函数返回的是一份值的拷贝,所以我们要传入变量的指针才可以
  • 因为传递的是一个指针,所以需要调用 Elem 方法找到这个指针指向的值,这样才能修改
  • 要修改一个变量的值,关键点:传递指针(可寻址),通过 Elem 方法获取指向的值,才可以保证值可以被修改,reflect.Value 为我们提供了 CanSet 方法判断是否可以修改该变量

修改 struct 结构体字段的值

func main() {
   p := person{Name: "微客鸟窝",Age: 18}
   pv:=reflect.ValueOf(&p)
   pv.Elem().Field(0).SetString("无尘")
   fmt.Println(p)
}
type person struct {
   Name string
   Age int
}
复制代码

步骤总结:

  1. 传递一个 struct 结构体的指针,获取对应的 reflect.Value;
  2. 通过 Elem 方法获取指针指向的值;
  3. 通过 Field 方法获取要修改的字段;
  4. 通过 Set 系列方法修改成对应的值。

通过反射修改一个值的规则:

  • 可被寻址,通俗地讲就是要向 reflect.ValueOf 函数传递一个指针作为参数。
  • 如果要修改 struct 结构体字段值的话,该字段需要是可导出的,而不是私有的,也就是该字段的首字母为大写。
  • 记得使用 Elem 方法获得指针指向的值,这样才能调用 Set 系列方法进行修改。

获取对应的底层类型

因为我们可以通过 type 关键字来自定义一些新的类型,而底层类型就是一些基础类型。比如上面示例中的 p 变量类型为 person,底层类型为 struct 结构体类型。

func main() {
   p := person{Name: "微客鸟窝",Age: 18}
   pv:=reflect.ValueOf(&p)
   fmt.Println(pv.Kind())
   pv:=reflect.ValueOf(p)
   fmt.Println(pv.Kind())
}
//运行结果:
ptr
struct
复制代码

Kind 方法返回一个 Kind 类型的值,它是一个常量,从源码看下定义的 Kind 常量列表,含了 Go 语言的所有底层类型:

type Kind uint
const (
   Invalid Kind = iota
   Bool
   Int
   Int8
   Int16
   Int32
   Int64
   Uint
   Uint8
   Uint16
   Uint32
   Uint64
   Uintptr
   Float32
   Float64
   Complex64
   Complex128
   Array
   Chan
   Func
   Interface
   Map
   Ptr
   Slice
   String
   Struct
   UnsafePointer
)
复制代码

reflect.Type

  • reflect.Type 是一个接口,而不是一个结构体,所以也只能使用它的方法。
  • reflect.Type 接口常用的方法
type Type interface {
   Implements(u Type) bool //方法用于判断是否实现了接口 u;
   AssignableTo(u Type) bool //方法用于判断是否可以赋值给类型 u,其实就是是否可以使用 =,即赋值运算符;
   ConvertibleTo(u Type) bool //方法用于判断是否可以转换成类型 u,其实就是是否可以进行类型转换;
   Comparable() bool //方法用于判断该类型是否是可比较的,其实就是是否可以使用关系运算符进行比较。
   //以下这些方法和Value结构体的功能相同
   Kind() Kind
   Method(int) Method
   MethodByName(string) (Method, bool)
   NumMethod() int
   Elem() Type
   Field(i int) StructField
   FieldByIndex(index []int) StructField
   FieldByName(name string) (StructField, bool)
   FieldByNameFunc(match func(string) bool) (StructField, bool)
   NumField() int
}
复制代码

遍历结构体的字段和方法

package main
import (
  "fmt"
  "reflect"
)
func main() {
  p := person{Name: "微客鸟窝", Age: 18}
  pt := reflect.TypeOf(p)
  //遍历person的字段
  for i := 0; i < pt.NumField(); i++ {
    fmt.Println("字段:", pt.Field(i).Name)
  }
  //遍历person的方法
  for i := 0; i < pt.NumMethod(); i++ {
    fmt.Println("方法:", pt.Method(i).Name)
  }
}
type person struct {
  Name string
  Age  int
}
func (p person) String() string{
  return fmt.Sprintf("Name is %s,Age is %d",p.Name,p.Age)
}
复制代码

运行结果:

字段: Name
字段: Age
方法: String
复制代码

是否实现某接口

//....
func main() {
  p := person{Name: "微客鸟窝", Age: 20}
  pt := reflect.TypeOf(p)
  stringerType := reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
  writerType := reflect.TypeOf((*io.Writer)(nil)).Elem()
  fmt.Println("是否实现了fmt.Stringer:", pt.Implements(stringerType))
  fmt.Println("是否实现了io.Writer:", pt.Implements(writerType))
}
//...
复制代码

运行结果:

是否实现了fmt.Stringer: true
是否实现了io.Writer: false
复制代码

JSON 和 Struct 互转

我们可以通过标准库中的 json 包进行JSON 和 Struct 互转:

//......
func main() {
  p := person{Name: "微客鸟窝", Age: 18}
  //struct to json
  jsonB, err := json.Marshal(p)
  if err == nil {
    fmt.Println(string(jsonB))
  }
  //json to struct
  respJSON := "{\"Name\":\"无尘\",\"Age\":18}"
  json.Unmarshal([]byte(respJSON), &p)
  fmt.Println(p)
}
//......
复制代码

运行结果:

{"Name":"微客鸟窝","Age":18}
Name is 无尘,Age is 18
复制代码
  • 通过 json.Marshal 函数,可以把 struct 转为 JSON 字符串。
  • 通过 json.Unmarshal 函数,可以把 JSON 字符串转为 struct。

Struct Tag

struct tag 是一个添加在 struct 字段上的标记,使用它进行辅助。要想获得字段上的 tag,就要先反射获得对应的字段,可以通过 Field 方法做到。该方法返回一个 StructField 结构体,它有一个字段是 Tag,存有字段的所有 tag。

示例:

//遍历person字段中key为json、bson的tag
for i:=0;i<pt.NumField();i++{
   sf:=pt.Field(i)
   fmt.Printf("字段%s上,json tag为%s\n",sf.Name,sf.Tag.Get("json"))
   fmt.Printf("字段%s上,bson tag为%s\n",sf.Name,sf.Tag.Get("bson"))
}
type person struct {
   Name string `json:"name" bson:"b_name"`
   Age int `json:"age" bson:"b_name"`
}
复制代码

运行结果:

字段Name上,key为json的tag为name
字段Name上,key为bson的tag为b_name
字段Age上,key为json的tag为age
字段Age上,key为bson的tag为b_name
复制代码

反射定律

Go 语言的作者在博客上总结了反射的三大定律:

  1. 任何接口值 interface{} 都可以反射出反射对象,也就是 reflect.Value 和 reflect.Type,通过函数 reflect.ValueOf 和 reflect.TypeOf 获得。
  2. 反射对象也可以还原为 interface{} 变量,也就是第 1 条定律的可逆性,通过 reflect.Value 结构体的 Interface 方法获得。
  3. 要修改反射的对象,该值必须可设置,也就是可寻址,参考上节课修改变量的值那一节的内容理解。

=

相关文章
|
4天前
|
监控 Linux PHP
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
52 20
|
2天前
|
存储 监控 算法
探秘员工泄密行为防线:基于Go语言的布隆过滤器算法解析
在信息爆炸时代,员工泄密行为对企业构成重大威胁。本文聚焦布隆过滤器(Bloom Filter)这一高效数据结构,结合Go语言实现算法,帮助企业识别和预防泄密风险。通过构建正常操作“指纹库”,实时监测员工操作,快速筛查可疑行为。示例代码展示了如何利用布隆过滤器检测异常操作,并提出优化建议,如调整参数、结合日志分析系统等,全方位筑牢企业信息安全防线,守护核心竞争力。
|
10天前
|
Go C语言
Go语言入门:分支结构
本文介绍了Go语言中的条件语句,包括`if...else`、`if...else if`和`switch`结构,并通过多个练习详细解释了它们的用法。`if...else`用于简单的条件判断;`if...else if`处理多条件分支;`switch`则适用于基于不同值的选择逻辑。特别地,文章还介绍了`fallthrough`关键字,用于优化重复代码。通过实例如判断年龄、奇偶数、公交乘车及成绩等级等,帮助读者更好地理解和应用这些结构。
34 14
|
25天前
|
存储 监控 算法
内网监控系统之 Go 语言布隆过滤器算法深度剖析
在数字化时代,内网监控系统对企业和组织的信息安全至关重要。布隆过滤器(Bloom Filter)作为一种高效的数据结构,能够快速判断元素是否存在于集合中,适用于内网监控中的恶意IP和违规域名筛选。本文介绍其原理、优势及Go语言实现,提升系统性能与响应速度,保障信息安全。
28 5
|
1月前
|
存储 安全 Go
Go语言中的map数据结构是如何实现的?
Go 语言中的 `map` 是基于哈希表实现的键值对数据结构,支持快速查找、插入和删除操作。其原理涉及哈希函数、桶(Bucket)、动态扩容和哈希冲突处理等关键机制,平均时间复杂度为 O(1)。为了确保线程安全,Go 提供了 `sync.Map` 类型,通过分段锁实现并发访问的安全性。示例代码展示了如何使用自定义结构体和切片模拟 `map` 功能,以及如何使用 `sync.Map` 进行线程安全的操作。
|
10天前
|
监控 关系型数据库 MySQL
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
19 0
|
3月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
131 2
|
2月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2月前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
2月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析

推荐镜像

更多