【golang/go语言】Go语言之反射

news2024/11/17 9:44:00

本文参考了李文周的博客——Go语言基础之反射。

一、反射初识

1. 什么是反射

在计算机科学中,反射是指计算机程序在运行时(run time)可以访问、检测和修改它本身状态和行为的一种能力。用比喻来说,反射就是程序在运行的时候能够观察并修改自己的行为。

2. 使用场景

  • 编写函数时,并不知道调用者传入的参数类型是什么,可能是没约定好,也可能是传入的类型很多,这些类型不能统一表示
  • 有时候需要根据某些条件调用哪个函数,此时需要对函数和函数的参数进行反射,在运行期间动态的执行函数

3. 缺点

  • 反射相关的代码难以阅读
  • Go语言作为一门静态语言,在编码过程中可以发现一些类型错误,但对于反射代码则是无能为力的
  • 反射对性能影响比较大,比正常代码运行速度慢一到两个数量级

二、reflect包

Go语言中的接口值都具有具体类型和具体类型的值两部分信息,而类型(Type)和值(Value)可以理解为定义在reflect包下的两个结构体reflect.Type和reflect.Value。

在这里插入图片描述

reflect包下有两个最基础的函数——TypeOf和ValueOf,分别用于获取对象的类型信息和值信息,也即reflect.Type和reflect.Value对象。

函数说明
func TypeOf(i any) Type获取对象的类型信息
func ValueOf(i any) Value获取对象的值信息

1. refelect.TypeOf函数

(1) TypeOf函数

通过relect.TypeOf()函数我们可以获取任意对象的类型信息,即reflect.Type对象。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var x float64 = 3.4
	fmt.Println("type:",reflect.TypeOf(x)) //type: float64
}

(2) Type和Kind

反射中的类型可以分为类型(Type)和种类(Kind)两种。类型(Type)是在变量声明时被赋予的类型(静态类型)或者运行时的类型(动态类型,又称具体类型)。而种类(Kind)是指数据底层的类型,种类是有限可枚举的。reflect包下定义的Kind类型包含以下几种:

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        // 底层指针
)

反射对象的Kind可以通过reflect.Value或reflect.Type类型对象的Kind()函数获取。reflect.Value或reflect.Type类型对象有很多同名函数,它们有些功能相同,有些在返回值有些许区别,本文后面对此有更多的例子。

2. reflect.ValueOf函数

(1) ValueOf函数

通过reflect.ValueOf()函数我们可以获取任意对象的值信息,即reflect.Value对象,其中包含了原始值的值信息。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var x float64 = 3.4
	fmt.Println("value:",reflect.ValueOf(x)) //value: 3.4
}

(2) Value转为原始值

除了可以通过ValueOf()函数将原始值转为reflect.Value对象外,还可以将reflect.Value对象转为原始值,reflect.Value类型提供的获取原始值的方法如下:

方法说明
func (v Value) Interface() interface {}将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
func (v Value) Int() int64将值以 int 类型返回,所有有符号整型均可以此方式返回
func (v Value) Uint() uint64将值以 uint 类型返回,所有无符号整型均可以此方式返回
func (v Value) Float() float64将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
func (v Value) Bool() bool将值以 bool 类型返回
func (v Value) Bytes() []bytes将值以字节数组 []bytes 类型返回
func (v Value) String() string将值以字符串类型返回

可以发现通过Interface方法获取原始值的方式是最为通用的一种。

3. isNil和isValid方法

由于函数参数传递是值拷贝,所以必须传递变量的地址才能修改变量的值。在反射中可以使用Elem()方法来获取指针对应的值。

(1) isNil方法

func (v Value) IsNil() bool

IsNil()报告v持有的值是否为nil,IsNil()常被用于判断指针是否为空。。v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic。

(2) isValid方法

func (v Value) IsValid() bool

IsValid()返回v是否持有一个值,IsValid()常被用于判定返回值是否有效。

(3) 代码示例

func main() {
	// *int类型空指针
	var a *int
	fmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil())
	// nil值
	fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid())
	// 实例化一个匿名结构体
	b := struct{}{}
	// 尝试从结构体中查找"abc"字段
	fmt.Println("不存在的结构体成员:", reflect.ValueOf(b).FieldByName("abc").IsValid())
	// 尝试从结构体中查找"abc"方法
	fmt.Println("不存在的结构体方法:", reflect.ValueOf(b).MethodByName("abc").IsValid())
	// map
	c := map[string]int{}
	// 尝试从map中查找一个不存在的键
	fmt.Println("map中不存在的键:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("娜扎")).IsValid())
}

三、结构体反射

如果反射对象的类型是结构体,反射类型reflect.Type和反射值reflect.Value对象提供了对应的方法,来获取结构体的字段信息和方法信息,并且可以通过反射来调用结构体的方法。

1. 与结构体字段、方法相关的方法

下表中所列举的方法,除了Call()函数之外,其他的都是reflect.Value和reflect.Type都有的,只不过reflect.Value的同名函数返回结果的类型都是reflect.Value对象。

方法说明
func (t Type) Field(i int) StructField根据索引,返回索引对应的结构体字段的信息。
func (t Type) NumField() int返回结构体成员字段数量。
func (t Type) FieldByName(name string) (StructField, bool)根据给定字符串返回字符串对应的结构体字段的信息。
func (t Type) FieldByIndex(index []int) StructField多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。
func (t Type) FieldByNameFunc(match func(string) bool) (StructField,bool)根据传入的匹配函数匹配需要的字段。
func (t Type) NumMethod() int返回该类型的方法集中方法的数目
func (t Type) Method(int) Method返回该类型方法集中的第i个方法
func (t Type) MethodByName(string) (Method, bool)根据方法名返回该类型方法集中的方法
func (v Value) Call([]Value) []Value根据传参调用结构体的方法

2. 获取结构体字段信息

(1) StructField类型

在上表中,有些方法的返回值类型为StructField类型,它是用来描述结构体的一个字段信息的,其定义如下:

type StructField struct {
    Name    string      // 字段的名字
    PkgPath string      // 非导出字段的包路径,对于导出字段该字段值为""
    Type      Type      // 字段的类型
    Tag       StructTag // 字段的标签
    Offset    uintptr   // 字段在结构体中的字节偏移量
    Index     []int     // 用于Type.FieldByIndex时的索引
    Anonymous bool      // 是否匿名字段
}

(2) 获取结构体字段信息步骤

获取结构体字段信息可以分为以下几步:

  • 先获取interface的reflect.Type,然后通过NumField进行遍历
  • 再通过reflect.Type的Field方法根据下标获取其Field
  • 最后通过reflect.Value的Field的Interface()得到对应的value

当然也可以使用FieldByName方法根据字段名获取字段信息。

2. 获取结构体方法信息

(1) Method类型

在上表中,有些方法的返回值类型为Method类型,它是用来描述结构体的一个方法信息的,其定义如下:

type Method struct {
	Name string    // 方法名称
	PkgPath string // 非导出方法的包路径,对于导出方法该字段值为""
	Type  Type     // 方法类型
	Func  Value    // 带有接收器作为第一个参数的方法
	Index int      // 用于Type.MethodByIndex时的索引
}

(2) 获取结构体方法信息步骤

获取结构体方法信息可以分为以下几步:

  • 先获取interface的reflect.Type,然后通过NumMethod进行遍历
  • 再通过reflect.Type的Method根据下标获取其Method
  • 使用reflect.Value的Call函数调用结构体的Method

当然也可以使用MethodByName方法根据方法名获取方法信息。

3. 代码示例

package main

import (
	"fmt"
	"reflect"
)

// 定义一个结构体
type Person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
	Sex  string `json:"sex"`
}

// 注意,如果是指针 *Person 类型,则不算做是 Person 的Method
func (p Person) PrintInfo() {
	fmt.Printf("name:%s, age:%d, sex:%s\n", p.Name, p.Age, p.Sex)
}

func (p Person) Say(msg string) {
	fmt.Println("hello,", msg)
}

func main() {
	p := Person{
		Name: "zuzhiang",
		Age:  27,
		Sex:  "female",
	}
	getFieldAndMethod(p)
}

// getFieldAndMethod 通过接口来获取任意参数,并打印结构体的字段和方法信息
func getFieldAndMethod(input interface{}) {
	getType := reflect.TypeOf(input)           // 获取input的类型
	fmt.Println("Type.Name: ", getType.Name()) // Person
	fmt.Println("Type.Kind: ", getType.Kind()) // struct

	getValue := reflect.ValueOf(input) // 获取input的值
	fmt.Println("Value:", getValue) // {zuzhiang 27 female}
  fmt.Println("----------------------------------------\n")

	// 获取结构体字段
	// 1. 先获取interface的reflect.Type,然后通过NumField进行遍历
	// 2. 再通过reflect.Type的Field方法根据下标获取其Field
	// 3. 最后通过reflect.Value的Field的Interface()得到对应的value
	for i := 0; i < getType.NumField(); i++ {
		field := getType.Field(i)
		value := getValue.Field(i).Interface() //获取第i个值
		fmt.Printf("字段名: %s, 字段类型: %s, 字段索引: %d, json tag: %s, 字段值: %v \n",
			field.Name, field.Type, field.Index, field.Tag.Get("json"), value)
	}
  fmt.Println("----------------------------------------\n")
	
  // 定义函数调用时的参数,Call函数的参数类型必须是[]reflect.Value
	msg := "zuzhiang"
	paramList := make([]reflect.Value, 0)
	paramList = append(paramList, reflect.ValueOf(msg))
	args := [][]reflect.Value{
		nil,
		paramList,
	}
  
  // 通过反射,操作方法
	// 1. 先获取interface的reflect.Type,然后通过NumMethod进行遍历
	// 2. 再通过reflect.Type的Method根据下标获取其Method
	// 3. 使用reflect.Value的Call函数调用结构体的Method
	for i := 0; i < getType.NumMethod(); i++ {
		method := getType.Method(i)
		fmt.Printf("方法名称: %s, 方法类型: %v \n", method.Name, method.Type)
    // 函数的顺序按函数名字典序正序排列
		getValue.Method(i).Call(args[i])
		fmt.Println("")
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/375330.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【genius_platform软件平台开发】第八十八讲:arm公司的V架构和内核发展史(整理)

1. ARM公司简介 ARM&#xff08;Advanced RISC Machines&#xff09;有三种含义&#xff1a;它是一个公司的名称、它是一类微处理器的通称、它是一种技术的名称。 ARM 公司是微处理器行业的一家知名企业&#xff0c;它是知识产权供应商&#xff0c;设计基于ARM体系的处理器&a…

5-Azidopentanoic acid,79583-98-5,5-Azidopentanoic COOH具有高效稳定,高特异性

5-Azidopentanoic acid&#xff0c;5-Azidopentanoic COOH&#xff0c;5-叠氮基戊酸产品规格&#xff1a;1.CAS号&#xff1a;79583-98-52.分子式&#xff1a;C5H9N3O23.分子量&#xff1a;143.074.包装规格&#xff1a;1g&#xff0c;5g&#xff0c;10g&#xff0c;包装灵活&a…

Spring Data JPA 之 理解 Persistence Context 的核心概念

21 理解 Persistence Context 的核心概念 21.1 Persistence Context 相关核心概念 21.1.1 EntityManagerFactory 和 Persistence Unit 按照 JPA 协议⾥⾯的定义&#xff1a;persistence unit 是⼀些持久化配置的集合&#xff0c;⾥⾯包含了数据源的配置、EntityManagerFacto…

WideDeep模型

google提出的Wide&deep模型&#xff0c;将线性模型与DNN很好的结合起来&#xff0c;在提高模型泛化能力的同时&#xff0c;兼顾模型的记忆性。wide&deep这种将线性模型与DNN的并行连接模式&#xff0c;后来称为推荐领域的经典模式&#xff0c;奠定了后面深度学习模型的…

Containerd容器运行时将会替换Docker?

文章目录一、什么是Containerd&#xff1f;二、Containerd有哪些功能&#xff1f;三、Containerd与Docker的区别四、Containerd是否会替换Docker&#xff1f;五、Containerd安装、部署和使用公众号&#xff1a; MCNU云原生&#xff0c;欢迎微信搜索关注&#xff0c;更多干货&am…

一条 SQL 查询语句是如何执行的?

MySQL是典型的C/S架构&#xff08;客户端/服务器架构&#xff09;&#xff0c;客户端进程向服务端进程发送一段文本&#xff08;MySQL指令&#xff09;&#xff0c;服务器进程进行语句处理然后返回执行结果。 问题来了。服务器进程对客户端发送的请求究竟做了什么处理呢&#…

【大数据基础】Hadoop3.1.3安装教程

来源&#xff1a; https://dblab.xmu.edu.cn/blog/2441/ 前言&#xff1a;重装解决一切bug&#xff01;事实上&#xff0c;问题中的绝大部分衍生问题都可以通过重装解决。 实验内容 创建Hadoop用户 首先按 ctrlaltt 打开终端窗口&#xff0c;输入如下命令创建新用户 : sudo…

【Spring6】| Spring对IoC的实现(核心重点)

目录 一&#xff1a;Spring对IoC的实现 1. IoC 控制反转 2. 依赖注入 2.1 set注入 2.2 构造注入 3. set注入专题 3.1 注入外部Bean 3.2 注入内部Bean 3.3 注入简单类型 3.4 级联属性赋值&#xff08;了解&#xff09; 3.5 注入数组 3.6 注入List集合和Set集合 3.7…

17- TensorFlow中使用Keras创建模型 (TensorFlow系列) (深度学习)

知识要点 Keras 是一个用 Python 编写的高级神经网络 API数据的开方: np.sqrt(784) # 28代码运行调整到 CPU 或者 GPU: import tensorflow as tf cputf.config.list_physical_devices("CPU") tf.config.set_visible_devices(cpu) 模型显示: model.summary()…

Tik Tok品牌营销,如何做好内容打法

TikTok 上做好品牌营销&#xff0c;并不能只关注品牌所获得的视频浏览量和点赞量&#xff0c;根据潜在客户需求生成的内容策略同样至关重要。通过建立营销漏斗模型&#xff0c;可以将 TikTok 策略分为三种不同类型的内容&#xff0c;从具有广泛吸引力的内容转变为具有高度针对性…

Vue组件是怎样挂载的

我们先来关注一下$mount是实现什么功能的吧&#xff1a; 我们打开源码路径core/instance/init.js: export function initMixin (Vue: Class<Component>) {......initLifecycle(vm)// 事件监听初始化initEvents(vm)initRender(vm)callHook(vm, beforeCreate)initInject…

FastDDS-3. DDS层

3. DDS层 eProsima Fast DDS公开了两个不同的API&#xff0c;以在不同级别与通信服务交互。主要API是数据分发服务&#xff08;DDS&#xff09;数据中心发布订阅&#xff08;DCPS&#xff09;平台独立模型&#xff08;PIM&#xff09;API&#xff0c;简称DDS DCPS PIM&#xf…

nacos集群模式+keepalived搭建高可用服务

实际工作中如果nacos这样的核心服务停掉了或者整个服务器宕机了&#xff0c;那整个系统也就gg了&#xff0c;所以像这样的核心服务我们必须要搞个3个或者3个以上的nacos集群部署&#xff0c;实现高可用&#xff1b; 部署高可用版本之前&#xff0c;首先你要会部署单机版的naco…

[2]MyBatis+Spring+SpringMVC+SSM整合一套通关

二、Spring 1、Spring简介 1.1、Spring概述 官网地址&#xff1a;https://spring.io/ Spring 是最受欢迎的企业级 Java 应用程序开发框架&#xff0c;数以百万的来自世界各地的开发人员使用 Spring 框架来创建性能好、易于测试、可重用的代码。 Spring 框架是一个开源的 Jav…

激活函数入门学习

本篇文章从外行工科的角度尽量详细剖析激活函数&#xff0c;希望不吝指教&#xff01; 学习过程如下&#xff0c;先知道这个东西是什么&#xff0c;有什么用处&#xff0c;以及怎么使用它&#xff1a; 1. 为什么使用激活函数 2. 激活函数总类及优缺点 3. 如何选择激活函数 …

一篇了解模块打包工具之 ——webpack(1)

本篇采用问题引导的方式来学习webpack&#xff0c;借此梳理一下自己对webpack的理解&#xff0c;将所有的知识点连成一条线&#xff0c;形成对webpack的记忆导图。 最终目标&#xff0c;手动构建一个vue项目&#xff0c;目录结构参考vue-cli创建出来的项目 一、问问题 1. 第…

Echarts 仪表盘倾斜一定角度显示,非中间对称

第024个点击查看专栏目录大多数的情况下&#xff0c;制作的仪表盘都是中规中矩&#xff0c;横向中间对称&#xff0c;但是生活中的汽车&#xff0c;摩托车等仪表盘确是要倾斜一定角度的&#xff0c;Echarts中我们就模拟一个带有倾斜角度的仪表盘。核心代码见示例源代码 文章目录…

搞明白redis的这些问题,你就是redis高手

什么是redis? Redis 本质上是一个 Key-Value 类型的内存数据库&#xff0c; 整个数据库加载在内存当中进行操作&#xff0c; 定期通过异步操作把数据库数据 flush 到硬盘上进行保存。 因为是纯内存操作&#xff0c; Redis 的性能非常出色&#xff0c; 每秒可以处理超过 10 万…

JS 快速创建二维数组 fill方法的坑点

JS 快速创建二维数组 坑 在算法中&#xff0c;创建二维数组遇到的一个坑 const arr new Array(5).fill(new Array(2).fill(1))我们如果想要修改其中一个元素的值 arr[0][1] 5我们可以发现所有数组中的第二个元素都发生了改变 查看MDN&#xff0c;我们会发现&#xff0c;当…

2023前端二面经典手写面试题

实现一个call call做了什么: 将函数设为对象的属性执行&删除这个函数指定this到函数并传入给定参数执行函数如果不传入参数&#xff0c;默认指向为 window // 模拟 call bar.mycall(null); //实现一个call方法&#xff1a; Function.prototype.myCall function(context…