【GO】一篇文章带你看透反射的原理

简介: 【GO】一篇文章带你看透反射的原理

反射是什么

  • 对于运行时内存中的任何一个对象,你不需要预先知道其类型是什么,也能访问其全部属性,调用其全部方法;
  • 反射的主要作用在于编写通用的框架;
  • 用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值。调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。Zero接受一个Type类型参数并返回一个代表该类型零值的Value类型值。


反射应用场景举例:导出商品列表到Excel


  • 需求是:不管用户在界面上看到什么商品,当它点击一下导出按钮,就将该商品的所有属性和值写出为文件;
  • 本例的难点是:我们无法预知用户会选择导出什么类型的商品数据、它有哪些属性,也就无法相应地去创建Excel数据表的列;
  • 因为商品的种类太多,如果用“正射”去做,那么有多少商品类型我们就要写多少个switch或if分支,然后在每一个分支里根据当前分支的具体商品类型去构造相应的数据列,这显然是狠蹩脚、狠难维护和扩展的;
  • 而通过反射来做就易如反掌了,管你用户要导出的是什么商品实例,我可以动态地解析其类型、动态获知其所有属性和方法,然后根据再根据其具体属性名称去创建相应的表格列,并将属性值填入其中;

然后我们开始看案例

首先需要定义俩个结构体

并且People继承PeopleParent

package main
import (
  "fmt"
  "reflect"
)
type PeopleParent struct {
  Kaka string
}
type People struct {
  PeopleParent
  Name string
  Age  int
}

定义一个方法为了在使用value接口演示使用


func (p People) Eat(name string) {
  fmt.Println("咔咔在吃什么呢!", name)
  p.Name = name
}


在main函数里边我们把People结构体的对象给创建出来


创建了机构对象并且复制给P 并且调用了typeAPI方法,把p传入进去

func main() {
  p := People{
  PeopleParent: PeopleParent{Kaka: "咔咔的父类属性"},
  Name:         "咔咔",
  Age:          24,
  }
  typeAPI(p)
  valueAPI(p)
}

开始我们的typeAPI的一些接口

func typeAPI(obj interface{}) {
  // 返回保存值的类型
  oType := reflect.TypeOf(obj)
  fmt.Println(oType) // main.People
  // 原始类型
  kind := oType.Kind()
  fmt.Println(kind) // struct
  // 类型名称
  fmt.Println(oType.Name()) // People
  // 属性和方法的个数
  fmt.Println(oType.NumField())  // 2
  fmt.Println(oType.NumMethod()) // 0
  // 获取全部属性
  for i := 0; i < oType.NumField(); i++ {
    structField := oType.Field(i)
    // name string    age int
    fmt.Println(structField.Name, structField.Type)
  }
  // 获取全部方法
  for i := 0; i < oType.NumMethod(); i++ {
    structMethod := oType.Method(i)
    fmt.Println(structMethod.Name, structMethod.Type)
  }
  // 获取父类的属性  []int{0, 0}获取第0个父类  第0个属性
  fmt.Println(oType.FieldByIndex([]int{0, 0}).Name)
}

value的一些接口

在这里我们需要注意的是方法传入的是结构体的值并非指针

在修改属性值时是传的指针,这点看清楚

我们看到的是elem也可以调用结构体的方法

我们代码的一开始是ValueOf§ 如果改为*p也是跟elem一样的

  valueOf := reflect.ValueOf(&p)
  byEat := valueOf.MethodByName("Eat")
  byEat.Call([]reflect.Value{reflect.ValueOf("西瓜")})
  fmt.Println(valueOf)
func valueAPI(p People) {
  valueOf := reflect.ValueOf(p)
  // 获取所有属性值
  for i := 0; i < valueOf.NumField(); i++ {
    value := valueOf.Field(i)
    // {}
    //咔咔
    //24
    fmt.Println(value)
  }
  // 获取父类属性
  fieldByIndex := valueOf.FieldByIndex([]int{0, 0})
  fmt.Println(fieldByIndex.Interface()) // 咔咔的父类属性
  // 获得指针value的内容进而获得成员的值
  valuePrt := reflect.ValueOf(&p)
  elem := valuePrt.Elem()
  value := elem.Field(0).Interface()
  fmt.Println(value) //{咔咔的父类属性}
  // 根据属性名获取值
  age := elem.FieldByName("Age")
  fmt.Println("咔咔的年龄", age) // 咔咔的年龄 24
  // 修改属性值
  elem.FieldByName("Age").SetInt(26)
  fmt.Println(elem) //{{咔咔的父类属性} 咔咔 26}
  // 调用对象的方法
  mValue := elem.MethodByName("Eat")
  // 参数需要反射
  mValue.Call([]reflect.Value{reflect.ValueOf("西瓜")})
  fmt.Println(elem) //咔咔在吃什么呢! 西瓜
}

完整代码

package main
import (
  "fmt"
  "reflect"
)
type PeopleParent struct {
  Kaka string
}
type People struct {
  PeopleParent
  Name string
  Age  int
}
func (p People) Eat(name string) {
  fmt.Println("咔咔在吃什么呢!", name)
  p.Name = name
}
func main() {
  p := People{
    PeopleParent: PeopleParent{Kaka: "咔咔的父类属性"},
    Name:         "咔咔",
    Age:          24,
  }
  typeAPI(p)
  valueAPI(p)
}
func typeAPI(obj interface{}) {
  // 返回保存值的类型
  oType := reflect.TypeOf(obj)
  fmt.Println(oType) // main.People
  // 原始类型
  kind := oType.Kind()
  fmt.Println(kind) // struct
  // 类型名称
  fmt.Println(oType.Name()) // People
  // 属性和方法的个数
  fmt.Println(oType.NumField())  // 2
  fmt.Println(oType.NumMethod()) // 0
  // 获取全部属性
  for i := 0; i < oType.NumField(); i++ {
    structField := oType.Field(i)
    // name string    age int
    fmt.Println(structField.Name, structField.Type)
  }
  // 获取全部方法
  for i := 0; i < oType.NumMethod(); i++ {
    structMethod := oType.Method(i)
    fmt.Println(structMethod.Name, structMethod.Type)
  }
  // 获取父类的属性  []int{0, 0}获取第0个父类  第0个属性
  fmt.Println(oType.FieldByIndex([]int{0, 0}).Name)
}
func valueAPI(p People) {
  valueOf := reflect.ValueOf(p)
  //valueOf := reflect.ValueOf(&p)
  //byEat := valueOf.MethodByName("Eat")
  //byEat.Call([]reflect.Value{reflect.ValueOf("西瓜")})
  //fmt.Println(valueOf)
  // 获取所有属性值
  for i := 0; i < valueOf.NumField(); i++ {
    value := valueOf.Field(i)
    // {}
    //咔咔
    //24
    fmt.Println(value)
  }
  // 获取父类属性
  fieldByIndex := valueOf.FieldByIndex([]int{0, 0})
  fmt.Println(fieldByIndex.Interface()) // 咔咔的父类属性
  // 获得指针value的内容进而获得成员的值
  valuePrt := reflect.ValueOf(&p)
  elem := valuePrt.Elem()
  value := elem.Field(0).Interface()
  fmt.Println(value) //{咔咔的父类属性}
  // 根据属性名获取值
  age := elem.FieldByName("Age")
  fmt.Println("咔咔的年龄", age) // 咔咔的年龄 24
  // 修改属性值
  elem.FieldByName("Age").SetInt(26)
  fmt.Println(elem) //{{咔咔的父类属性} 咔咔 26}
  // 调用对象的方法
  mValue := elem.MethodByName("Eat")
  // 参数需要反射
  mValue.Call([]reflect.Value{reflect.ValueOf("西瓜")})
  fmt.Println(elem) //咔咔在吃什么呢! 西瓜
}
相关文章
|
8天前
|
负载均衡 监控 Go
Golang深入浅出之-Go语言中的服务网格(Service Mesh)原理与应用
【5月更文挑战第5天】服务网格是处理服务间通信的基础设施层,常由数据平面(代理,如Envoy)和控制平面(管理配置)组成。本文讨论了服务发现、负载均衡和追踪等常见问题及其解决方案,并展示了使用Go语言实现Envoy sidecar配置的例子,强调Go语言在构建服务网格中的优势。服务网格能提升微服务的管理和可观测性,正确应对问题能构建更健壮的分布式系统。
28 1
|
12天前
|
JSON 监控 安全
Golang深入浅出之-Go语言中的反射(reflect):原理与实战应用
【5月更文挑战第1天】Go语言的反射允许运行时检查和修改结构,主要通过`reflect`包的`Type`和`Value`实现。然而,滥用反射可能导致代码复杂和性能下降。要安全使用,应注意避免过度使用,始终进行类型检查,并尊重封装。反射的应用包括动态接口实现、JSON序列化和元编程。理解反射原理并谨慎使用是关键,应尽量保持代码静态类型。
23 2
|
13天前
|
Go
go 反射Reflect
go 反射Reflect
|
13天前
|
Go 数据处理
【Go 语言专栏】Go 语言的反射机制及其应用
【4月更文挑战第30天】Go语言的反射机制通过`reflect`包实现,允许运行时检查和操作类型信息。核心概念包括`reflect.Type`(表示类型)和`reflect.Value`(表示值)。主要操作包括获取类型信息、字段信息及动态调用方法。反射适用于通用数据处理、序列化、动态配置和代码生成等场景,但也带来性能开销和维护难度,使用时需谨慎。通过实例展示了如何使用反射处理不同类型数据,强调了在理解和应用反射时需要不断实践。
|
28天前
|
Java Go 调度
Go语言并发编程原理与实践:面试经验与必备知识点解析
【4月更文挑战第12天】本文分享了Go语言并发编程在面试中的重要性,包括必备知识点和面试经验。核心知识点涵盖Goroutines、Channels、Select、Mutex、Sync包、Context和错误处理。面试策略强调结构化回答、代码示例及实战经历。同时,解析了Goroutine与线程的区别、Channel实现生产者消费者模式、避免死锁的方法以及Context包的作用和应用场景。通过理论与实践的结合,助你成功应对Go并发编程面试。
25 3
|
1月前
|
存储 Go 索引
掌握Go语言:深入理解Go语言数组,基本原理与示例解析(15)
掌握Go语言:深入理解Go语言数组,基本原理与示例解析(15)
|
2月前
|
存储 算法 Java
Go语言GC(垃圾回收)的工作原理
【2月更文挑战第23天】
39 0
|
2月前
|
安全 Go 开发者
Go语言中的反射机制深度解析
【2月更文挑战第19天】Go语言的反射机制提供了一种在运行时检查对象类型、获取和设置对象字段、调用对象方法的能力。本文通过深入解析Go语言反射机制的核心概念、使用方法和注意事项,帮助读者更好地理解和应用这一强大而灵活的工具。
|
3月前
|
Go 开发者
Go语言错误处理机制:原理与实践
【2月更文挑战第7天】在Go语言中,错误处理是一项核心特性。Go语言鼓励显式的错误检查,而不是依赖于异常机制。本文将深入探讨Go语言的错误处理机制,包括错误的表示、传播和处理方式,以及如何在日常编程中有效地应用这一机制,确保代码的健壮性和可读性。
34 10
|
3月前
|
算法 Java Go
Go语言中的垃圾回收机制:原理、优化与影响
【2月更文挑战第5天】本文深入探讨了Go语言中的垃圾回收机制,包括其工作原理、性能优化方法以及对程序运行的影响。通过了解这些内容,读者可以更好地理解Go语言的内存管理特点,并在实际开发中更好地应对垃圾回收带来的挑战。