Go:反射

为什么使用反射

在编程中,有时需编写函数统一处理多种值类型 ,这些类型可能无法共享同一接口、布局未知,甚至在设计函数时还不存在 。

func Sprint(x interface{}) string {type stringer interface {String() string}switch x := x.(type) {case stringer:return x.String()case string:return xcase int:return strconv.Itoa(x)//...对 int16、uint32 等类型做类似的处理case bool:if x {return "true"}return "false"default:// array、chan、func、map、pointer、slice、structreturn "???"}
}

以实现类似fmt.SprintfSprint函数为例,该函数接收一个参数并返回字符串 。初步实现思路是:

  • 首先判断参数是否实现了String方法,若实现则直接调用 。
  • 然后通过switch语句判断参数动态类型是否为基本类型(如stringintbool等 ),针对不同基本类型进行格式化操作 。如string类型直接返回原值;int类型通过strconv.Itoa转换为字符串;bool类型根据值返回"true""false"
  • 对于默认情况(如arraychanfuncmappointerslicestruct等类型 ),简单返回"???"

但对于更复杂类型(如[]float64map[string][]string )以及自定义类型(如url.Values ),仅靠上述分支处理会面临问题 。因为类型数量无限,难以添加所有分支;且即使添加了处理某底层类型的分支,也无法处理具有该底层类型的自定义类型,还可能因引入自定义类型处理分支导致库的循环引用 。当无法知晓未知类型的布局时,这种基于类型分支的代码就难以继续编写,此时就需要借助反射机制来解决。

reflect.Type 和 reflect.Value

reflect.Type

  • 功能与定义reflect包提供反射功能,reflect.Type表示 Go 语言的一个类型,是有多种方法的接口,可识别类型、透视类型组成部分(如结构体字段、函数参数 ) 。reflect.TypeOf函数接收interface{}参数,返回接口中动态类型的reflect.Type形式 。
// reflect.Type相关示例
t := reflect.TypeOf(3) 
fmt.Println(t.String()) 
fmt.Println(t) var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) fmt.Printf("%T\n", 3) 
  • 示例:如reflect.TypeOf(3)返回表示int类型的reflect.Typefmt.Printf("%T\n", 3)内部实现就使用了reflect.TypeOf 。当变量实现接口类型转换时,reflect.TypeOf返回具体类型而非接口类型,如var w io.Writer = os.Stdoutreflect.TypeOf(w)返回*os.File

reflect.Value

  • 功能与定义reflect.Value可包含任意类型的值 。reflect.ValueOf函数接收interface{}参数,将接口动态值以reflect.Value形式返回 。
// reflect.Value相关示例
v := reflect.ValueOf(3) 
fmt.Println(v) 
fmt.Printf("%v\n", v) 
fmt.Println(v.String()) t := v.Type() 
fmt.Println(t.String()) v := reflect.ValueOf(3) 
x := v.Interface()
i := x.(int)
fmt.Printf("%d\n", i) 
  • 示例reflect.ValueOf(3)返回包含值3reflect.Valuereflect.Value满足fmt.Stringer ,但非字符串时String方法仅暴露类型 ,常用fmt%v功能处理 。reflect.ValueType方法可返回其类型(reflect.Type形式 ),reflect.Value.Interface方法是reflect.ValueOf的逆操作,返回含相同具体值的interface{}

示例

// 格式化函数示例
package formatimport ("reflect""strconv"
)func Any(value interface{}) string {return formatAtom(reflect.ValueOf(value))
}func formatAtom(v reflect.Value) string {switch v.Kind() {case reflect.Invalid:return "invalid"case reflect.Int, reflect.Int8, reflect.Int16,reflect.Int32, reflect.Int64:return strconv.FormatInt(v.Int(), 10)case reflect.Uint, reflect.Uint8, reflect.Uint16,reflect.Uint32, reflect.Uint64, reflect.Uintptr:return strconv.FormatUint(v.Uint(), 10)//...为简化起见,省略了浮点数和复数的分支...case reflect.Bool:return strconv.FormatBool(v.Bool())case reflect.String:return strconv.Quote(v.String())case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:return v.Type().String() + " 0x" +strconv.FormatUint(uint64(v.Pointer()), 16)default: // reflect.Array, reflect.Struct, reflect.Interfacereturn v.Type().String() + " value"}
}

利用reflect.ValueKind方法区分类型,编写通用格式化函数format.Anyformat.Any调用formatAtomformatAtom通过switch v.Kind()判断类型并格式化 ,涵盖基础类型(BoolString 、各种数字类型 )、聚合类型(ArrayStruct )、引用类型(ChanFuncPtrSliceMap )、接口类型(Interface )及Invalid类型 。当前版本把值当作不可分割物体处理,对聚合类型和接口仅输出类型,对引用类型输出类型和引用地址,虽不够理想但有进步,且对命名类型效果较好 。

Display:一个递归的值显示器

Display是调试工具函数,接收任意复杂值x ,输出其完整结构及元素路径 。为避免在包 API 中暴露反射相关内容,定义未导出的display函数做递归处理,Display仅为简单封装 。Display函数接收interface{}参数,内部调用displaydisplay使用之前定义的formatAtom函数输出基础值,并通过reflect.Value的方法递归展示复杂类型组成部分 。

处理逻辑

// 主函数,用于封装和暴露功能
func Display(name string, x interface{}) {fmt.Printf("Display %s (%T):\n", name, x)display(name, reflect.ValueOf(x))
}
// 实际递归处理的函数
func display(path string, v reflect.Value) {switch v.Kind() {case reflect.Invalid:fmt.Printf("%s = invalid\n", path)case reflect.Array:for i := 0; i < v.Len(); i++ {display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))}case reflect.Struct:for i := 0; i < v.NumField(); i++ {fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name)display(fieldPath, v.Field(i))}case reflect.Map:for _, key := range v.MapKeys() {display(fmt.Sprintf("%s[%s]", path, formatAtom(key)), v.MapIndex(key))}case reflect.Ptr:if v.IsNil() {fmt.Printf("%s = nil\n", path)} else {display(fmt.Sprintf("(*%s)", path), v.Elem())}case reflect.Interface:if v.IsNil() {fmt.Printf("%s = nil\n", path)} else {fmt.Printf("%s.type = %s\n", path, v.Elem().Type())display(path+".value", v.Elem())}default: // 基本类型、通道、函数fmt.Printf("%s = %s\n", path, formatAtom(v))}
}
  • Invalid类型:若reflect.ValueInvalid类型,输出%s = invalid
  • 数组与切片Len方法获取元素个数,通过Index(i)方法按索引遍历,递归调用display ,在路径后加上[i]
  • 结构体NumField()方法获取字段数,借助reflect.TypeField(i)获取字段名,v.Field(i)获取字段值,递归调用display ,路径加上类似.f的字段选择标记 。
  • 映射(mapMapKeys方法返回键的reflect.Value切片,MapIndex(key)获取键对应的值,递归调用display ,路径追加[key]
  • 指针Elem方法返回指针指向变量,IsNil判断指针是否为空,为空输出%s = nil ,非空则递归调用display ,路径加*和圆括号 。
  • 接口IsNil判断接口是否为空,非空通过v.Elem()获取动态值,递归输出类型和值 。
  • 其他(基础类型、通道、函数 ):使用formatAtom格式化输出 。

使用 reflect.Value 来设置值

Go 语言中,xx.f[i]*p等表达式表示变量,可寻址存储区域包含值且可更新 ;x+1f(2)等不表示变量 。reflect.Value也有可寻址之分 ,通过示例x := 2等变量声明,说明reflect.ValueOf返回的一些值不可寻址(如abc ),但可通过指针间接获取可寻址的reflect.Value(如d := c.Elem() ) 。可使用CanAddr方法询问reflect.Value是否可寻址 。

获取可寻址变量

// 通过指针间接获取可寻址的reflect.Value并更新值
x = 2
d = reflect.ValueOf(&x).Elem()
px := d.Addr().Interface().(*int)
*px = 3
fmt.Println(x) // 3

获取可寻址的reflect.Value分三步:

  1. 调用Addr()返回含指向变量指针的Value
  2. 在该Value上调用Interface()返回含指针的interface{}值 。
  3. 使用类型断言将接口内容转换为普通指针,进而更新变量 。

更新变量的方式

// 直接通过可寻址的reflect.Value更新值
d.Set(reflect.ValueOf(4))
fmt.Println(x) // 4
  • 可直接通过可寻址的reflect.Value调用Set方法更新变量 ,运行时Set方法检查可赋值性,如变量类型为int ,值类型不匹配会崩溃 。
// 基本类型特化的Set变种使用示例
d = reflect.ValueOf(&x).Elem()
d.SetInt(3)
fmt.Println(x) // 3
  • 有针对基本类型的Set变种方法,如SetIntSetUintSetStringSetFloat等 ,有一定容错性,但在指向interface{}变量的reflect.Value上调用SetInt会崩溃 。

反射可读取未导出结构字段值(如os.Filefd字段 ),但不能更新 。可寻址的reflect.Value记录是否通过遍历未导出字段获得,修改其值前用CanAddr检查不一定准确,需用CanSet方法正确报告reflect.Value是否可寻址且可更改 。

显示类型的方法

package mainimport ("fmt""reflect""strings""time"
)// Print 输出值 x 的所有方法
func Print(x interface{}) {v := reflect.ValueOf(x)t := v.Type()fmt.Printf("type %s\n", t)for i := 0; i < v.NumMethod(); i++ {methType := v.Method(i).Type()fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name,strings.TrimPrefix(methType.String(), "func"))}
}

Print函数接收interface{}参数,通过reflect.ValueOf获取值的reflect.Value ,再用v.Type()获取reflect.Type 。先打印值的类型,然后遍历类型的方法 。通过v.NumMethod()获取方法数量,v.Method(i).Type()获取方法类型 。reflect.Typereflect.Value都有Method方法 ,从reflect.Type调用Method返回reflect.Method实例,描述方法名称和类型;从reflect.Value调用Method返回reflect.Value ,代表绑定接收者的方法 。最后按格式输出方法签名 。

注意事项

脆弱性

反射功能强大,但基于反射的代码很脆弱 。编译器能在编译时报告类型错误,而反射错误在运行时才以崩溃方式呈现,可能在代码编写很久后才暴露 。

代码理解难度

类型本身可作为一种文档,反射操作无法进行静态类型检查 ,大量使用反射的代码难以理解 。对于接收interface{}reflect.Value的函数,需明确期望的参数类型和限制条件 。

性能问题

基于反射的函数比针对特定类型优化的函数慢一两个数量级 。在程序中,非关键路径函数为代码清晰可用反射,测试因使用小数据集也适合反射;但关键路径上的函数应避免使用反射,以保证性能 。

参考资料:《Go程序设计语言》

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

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

相关文章

SS25001-多路复用开关板

1 概述 1.1 简介 多路复用开关板是使用信号继电器实现2线制的多路复用开关板卡&#xff1b;多路复用开关是一种可以将一个输入连接到多个输出或一个输出连接到多个输入的拓扑结构。这种拓扑通常用于扫描&#xff0c;适合将一系列通道自动连接到公共线路的的设备。多路复用开…

vue3 nprogress 使用

nprogress 介绍与作用 1.nprogress 是一个轻量级的进度条组件&#xff0c;主要用于在页面加载或路由切换时显示一个进度条&#xff0c;提升用户体验。它的原理是通过在页面顶部创建一个 div&#xff0c;并使用 fixed 定位来实现进度条的效果 2.在 Vite Vue 3 项目中&#xf…

Jsp技术入门指南【六】jsp脚本原理及隐式对象

Jsp技术入门指南【六】jsp脚本原理及隐式对象 前言一、JSP 脚本元素1.1 声明1.2 表达式1.3 脚本标签 二、JSP 的隐式对象是什么三、隐式对象详解outrequestsessionapplicationconfigexception 前言 在之前的博客中&#xff0c;我们已经介绍了JSP的环境搭建、编译文件查找以及生…

vue3推荐的移动table库

vxe-table https://gitee.com/js-class/vxe-table#https://gitee.com/link?targethttps%3A%2F%2Fvxetable.cn 文档api https://vxetable.cn/#/component/table/other/bookkeepingVoucher 引入步骤 安装 npm install xe-utils vxe-tablenext 在项目main.js引入 import …

HOOPS Exchange 与HOOPS Communicator集成:打造工业3D可视化新标杆!

一、概述 在工业3D开发、BIM建筑、数字孪生和仿真分析等高端应用场景中&#xff0c;数据格式复杂、模型体量庞大、实时交互体验要求高&#xff0c;一直是困扰开发者的难题。Tech Soft 3D旗下的HOOPS Exchange和HOOPS Communicator&#xff0c;正是解决这类问题的黄金搭档。二者…

《软件设计师》复习笔记(14.3)——设计模式

目录 一、设计模式分类 1. 创建型模式&#xff08;Creational Patterns&#xff09; 2. 结构型模式&#xff08;Structural Patterns&#xff09; 3. 行为型模式&#xff08;Behavioral Patterns&#xff09; 真题示例&#xff1a; 一、设计模式分类 架构模式 高层设计决…

HarmonyOS:使用Refresh组件实现页面下拉刷新上拉加载更多

一、前言 可以进行页面下拉操作并显示刷新动效的容器组件。 说明 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。该组件从API Version 12开始支持与垂直滚动的Swiper和Web的联动。当Swiper设置loop属性为true时&…

55、⾸屏加载⽩屏怎么进⾏优化

答&#xff1a; &#xff08;1&#xff09;使⽤CDN 减⼩代码体积&#xff0c;加快请求速度&#xff1b; (2)SSR通过服务端把所有数据全部渲染完成再返回给客⼾端&#xff1b; (3) 路由懒加载&#xff0c;当⽤⼾访问的时候&#xff0c;再加载相应模块&#xff1b; (4) 使⽤外…

什么是Python单例模式

什么是Python单例模式 Python单例模式是一种创建型设计模式,目的是确保一个类仅有一个实例,并提供一个全局访问点来获取该实例。以下从作用和示例进行介绍: 作用 控制资源使用:避免对系统资源的重复消耗,像数据库连接、文件句柄等稀缺资源,只创建一个实例来管理使用,防…

Java 2025:解锁未来5大技术趋势,Kotlin融合AI新篇

各位Java开发者们好&#xff01;&#x1f680; 2025年的Java世界正在经历一场前所未有的技术变革。作为深耕Java领域多年的技术博主&#xff0c;今天我将带大家深入探索Java生态即将迎来的5大技术趋势&#xff0c;特别是Kotlin的深度融合和AI技术的新篇章。准备好了吗&#xff…

计算机视觉cv2入门之车牌号码识别

前边我们已经讲解了使用cv2进行图像预处理与边缘检测等方面的知识&#xff0c;这里我们以车牌号码识别这一案例来实操一下。 大致思路 车牌号码识别的大致流程可以分为这三步&#xff1a;图像预处理-寻找车牌轮廓-车牌OCR识别 接下来我们按照这三步来进行讲解。 图像预处理 …

CExercise_13_1排序算法_3快速排序算法,包括单向分区以及双向分区

题目&#xff1a; 请手动实现快速排序算法&#xff0c;包括单向分区以及双向分区&#xff1a; // 单向分区快速排序算法 void quick_sort_one_way(int arr[], int len); //双向分区快速排序算法 void quick_sort_two_way(int arr[], int len); 关键点 分析&#xff1a; &#x…

FPGA-VGA

目录 前言 一、VGA是什么&#xff1f; 二、物理接口 三、VGA显示原理 四、VGA时序标准 五、VGA显示参数 六、模块设计 七、波形图设计 八、彩条波形数据 前言 VGA的FPGA驱动 一、VGA是什么&#xff1f; VGA&#xff08;Video Graphics Array&#xff09;是IBM于1987年推出的…

Linux和Ubuntu的驱动适配情况

旧 一、Linux Yocto3.0 二、Ubuntu 1.驱动 1.rtc正常 2.led正常 3.加密芯片正常 4.硬件看门狗不行&#xff0c;驱动已经适配好&#xff0c;等硬件修复后&#xff0c;直接使用脚本就可以 5.千兆网口可以&#xff0c;两个百兆网口不行 6.USB上面和下面都可以&#xff08;插u盘…

Python 文本和字节序列(处理文本文件)

本章将讨论下述话题&#xff1a; 字符、码位和字节表述 bytes、bytearray 和 memoryview 等二进制序列的独特特性 全部 Unicode 和陈旧字符集的编解码器 避免和处理编码错误 处理文本文件的最佳实践 默认编码的陷阱和标准 I/O 的问题 规范化 Unicode 文本&#xff0c;进行安全的…

【Android学习记录】工具使用

文章目录 一. 精准找视图资源ID1. 准备工作2. 使用 uiautomator 工具2.1. 获取设备的窗口内容2.2. Pull XML 文件2.3. 查看 XML 文件 3. 直接使用 ADB 命令4. 使用 Android Studio 的 Layout Inspector总结 二. adb shell dumpsys activity1. 如何使用 ADB 命令2. 输出内容解析…

Kafka系列之:计算kafka集群topic占的存储大小

Kafka系列之:计算kafka集群topic占的存储大小 topic存储数据格式统计topic存储大小定时统计topic存储大小topic存储数据格式 单位是字节大小 size_bytes{directory="/data/datum/kafka/optics-all" } 782336计算topic存储大小脚本逻辑是: 计算指定目录或文件的大小…

C# 高级编程:Lambda 表达式

在 C# 的高级编程中,Lambda 表达式是一个强大而灵活的工具,广泛应用于 LINQ 查询、委托、事件处理以及函数式编程等多个领域。它不仅使代码更简洁、表达更直接,而且在某些场景中能极大提高代码的可读性与可维护性。本文将从 Lambda 表达式的基本语法入手,深入探讨其原理、常…

《软件设计师》复习笔记(11.5)——测试原则、阶段、测试用例设计、调试

目录 1. 测试基础概念 2. 测试方法分类 3. 测试阶段 真题示例&#xff1a; 题目1 题目2 题目3 4. 测试策略 5. 测试用例设计 真题示例&#xff1a; 6. 调试与度量 真题示例&#xff1a; 1. 测试基础概念 定义&#xff1a;系统测试是为发现错误而执行程序的过程&…

方案解读:虚拟电厂标杆项目整体建设方案【附全文阅读】

在电力市场背景下,传统电力现货市场存在电能定价不合理、分布式电源并网困难等问题。本虚拟电厂标杆项目旨在研究全时间尺度虚拟电厂智能管控关键技术,通过研制虚拟电厂控制器样机、开发运行管理平台,实现对分布式能源的合理优化配置。项目内容涵盖虚拟调控、建设目标、建设…