go语言中的反射(一)

简介: go语言中的反射

Go 语言反射的三大法则:

  • 从 interface{} 变量可以反射出反射对象;
  • 从反射对象可以获取 interface{} 变量;
  • 要修改反射对象,其值必须可设置;

从反射对象到接口值的过程就是从接口值到反射对象的镜面过程,两个过程都需要经历两次转换:

  • 从接口值到反射对象:
  • 从基本类型到接口类型的类型转换;
  • 从接口类型到反射对象的转换;
  • 从反射对象到接口值:
  • 反射对象转换成接口类型;
  • 通过显式类型转换变成原始类型;

reflect.Type和reflect.Value

我们通过reflect.TypeOf、reflect.ValueOf可以将一个普通的变量转换成『反射』包中提供的Type和Value。

类型 Type 是反射包定义的一个接口,可以使用 reflect.TypeOf 函数获取任意变量的的类型,Type 接口中定义了一些有趣的方法,MethodByName 可以获取当前类型对应方法的引用、Implements 可以判断当前类型是否实现了某个接口:

type Type interface {
    // 变量的内存对齐,返回 rtype.align
    Align() int
    // struct 字段的内存对齐,返回 rtype.fieldAlign
    FieldAlign() int
    // 根据传入的 i,返回方法实例,表示类型的第 i 个方法
    Method(int) Method
    // 根据名字返回方法实例,这个比较常用
    MethodByName(string) (Method, bool)
    // 返回类型方法集中可导出的方法的数量
    NumMethod() int
    // 只返回类型名,不含包名
    Name() string
    // 返回导入路径,即 import 路径
    PkgPath() string
    // 返回 rtype.size 即类型大小,单位是字节数
    Size() uintptr
    // 返回类型名字,实际就是 PkgPath() + Name()
    String() string
    // 返回 rtype.kind,描述一种基础类型
    Kind() Kind
    // 检查当前类型有没有实现接口 u
    Implements(u Type) bool
    // 检查当前类型能不能赋值给接口 u
    AssignableTo(u Type) bool
    // 检查当前类型能不能转换成接口 u 类型
    ConvertibleTo(u Type) bool
    // 检查当前类型能不能做比较运算,其实就是看这个类型底层有没有绑定 typeAlg 的 equal 方法。
    // 打住!不要去搜 typeAlg 是什么,不然你会陷进去的!先把本文看完。
    Comparable() bool
    // 返回类型的位大小,但不是所有类型都能调这个方法,不能调的会 panic
    Bits() int
    // 返回 channel 类型的方向,如果不是 channel,会 panic
    ChanDir() ChanDir
    // 返回函数类型的最后一个参数是不是可变数量的,"..." 就这样的,同样,如果不是函数类型,会 panic
    IsVariadic() bool
    // 返回所包含元素的类型,只有 Array, Chan, Map, Ptr, Slice 这些才能调,其他类型会 panic。
    // 这不是废话吗。。其他类型也没有包含元素一说。
    Elem() Type
    // 返回 struct 类型的第 i 个字段,不是 struct 会 panic,i 越界也会 panic
    Field(i int) StructField
    // 跟上边一样,不过是嵌套调用的,比如 [1, 2] 就是说返回当前 struct 的第1个struct 的第2个字段,适用于 struct 本身嵌套的类型
    FieldByIndex(index []int) StructField
    // 按名字找 struct 字段,第二个返回值 ok 表示有没有
    FieldByName(name string) (StructField, bool)
    // 按函数名找 struct 字段,因为 struct 里也可能有类型是 func 的嘛
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    // 返回函数第 i 个参数的类型,不是 func 会 panic
    In(i int) Type
    // 返回 map 的 key 的类型,不是 map 会 panic
    Key() Type
    // 返回 array 的长度,不是 array 会 panic
    Len() int
    // 返回 struct 字段数量,不是 struct 会 panic
    NumField() int
    // 返回函数的参数数量,不是 func 会 panic
    NumIn() int
    // 返回函数的返回值数量,不是 func 会 panic
    NumOut() int
    // 返回函数第 i 个返回值的类型,不是 func 会 panic
    Out(i int) Type
}

反射包中 Value 的类型与 Type 不同,它被声明成了结构体。这个结构体没有对外暴露的字段,但是提供了获取或者写入数据的方法:

type Value struct {
    // 反射出来此值的类型,rtype 是啥往上看,但可别弄错了,这 typ 是未导出的,从外部调不到 Type 接口的方法
    typ *rtype
    // 数据形式的指针值
    ptr unsafe.Pointer
    // 保存元数据
    flag
}
// 前提 v 是一个 func,然后调用 v,并传入 in 参数,第一个参数是 in[0],第二个是 in[1],以此类推
func (v Value) Call(in []Value) []Value
// 返回 v 的接口值或者指针
func (v Value) Elem() Value
// 前提 v 是一个 struct,返回第 i 个字段,这个主要用于遍历
func (v Value) Field(i int) Value
// 前提 v 是一个 struct,根据字段名直接定位返回
func (v Value) FieldByName(name string) Value
// 前提 v 是 Array, Slice, String 之一,返回第 i 个元素,主要也是用于遍历,注意不能越界
func (v Value) Index(i int) Value
// 判断 v 是不是 nil,只有 chan, func, interface, map, pointer, slice 可以用,其他类型会 panic
func (v Value) IsNil() bool
// 判断 v 是否合法,如果返回 false,那么除了 String() 以外的其他方法调用都会 panic,事前检查是必要的
func (v Value) IsValid() bool
// 前提 v 是个 map,返回对应 value
func (v Value) MapIndex(key Value)
// 前提 v 是个 map,返回所有 key 组成的一个 slice
func (v Value) MapKeys() []Value
// 前提 v 是个 struct,返回字段个数
func (v Value) NumField() int
// 赋值
func (v Value) Set(x Value)
// 类型
func (v Value) Type() Type

实践

遍历一个结构体的字段以及对应的值

package main
  import (
      "fmt"
      "reflect"
  )
  type Person struct {
      Name     string
      Sex      string
      Age      int
      PhoneNum string
      School   string
      City     string
  }
func main() {
      p1 := Person{
          Name:     "tom",
          Sex:      "male",
          Age:      10,
          PhoneNum: "1000000",
          School:   "spb-kindergarden",
          City:     "cq",
      }
      rv := reflect.ValueOf(p1)
      rt := reflect.TypeOf(p1)
      if rv.Kind() == reflect.Struct {
          for i := 0; i < rt.NumField(); i++ {
              //按顺序遍历
              fmt.Printf("field:%+v,value:%+v\n", rt.Field(i).Name, rv.Field(i))
          }
      }
  }


go语言中的反射(二)https://developer.aliyun.com/article/1391508

相关文章
|
18天前
|
存储 Go 索引
go语言中数组和切片
go语言中数组和切片
27 7
|
17天前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
18天前
|
程序员 Go
go语言中结构体(Struct)
go语言中结构体(Struct)
92 71
|
17天前
|
存储 Go 索引
go语言中的数组(Array)
go语言中的数组(Array)
100 67
|
20天前
|
Go 索引
go语言for遍历数组或切片
go语言for遍历数组或切片
90 62
|
22天前
|
并行计算 安全 Go
Go语言中的并发编程:掌握goroutines和channels####
本文深入探讨了Go语言中并发编程的核心概念——goroutine和channel。不同于传统的线程模型,Go通过轻量级的goroutine和通信机制channel,实现了高效的并发处理。我们将从基础概念开始,逐步深入到实际应用案例,揭示如何在Go语言中优雅地实现并发控制和数据同步。 ####
|
18天前
|
存储 Go
go语言中映射
go语言中映射
32 11
|
20天前
|
Go
go语言for遍历映射(map)
go语言for遍历映射(map)
30 12
|
19天前
|
Go 索引
go语言使用索引遍历
go语言使用索引遍历
27 9
|
23天前
|
安全 Serverless Go
Go语言中的并发编程:深入理解与实践####
本文旨在为读者提供一个关于Go语言并发编程的全面指南。我们将从并发的基本概念讲起,逐步深入到Go语言特有的goroutine和channel机制,探讨它们如何简化多线程编程的复杂性。通过实例演示和代码分析,本文将揭示Go语言在处理并发任务时的优势,以及如何在实际项目中高效利用这些特性来提升性能和响应速度。无论你是Go语言的初学者还是有一定经验的开发者,本文都将为你提供有价值的见解和实用的技巧。 ####