【go语言】reflect包与类型推断

reflect 包的核心概念

Go 中的反射涉及两个核心概念:

  • Type:表示一个类型的结构体,reflect.Type 是类型的描述。
  • Value:表示一个值的结构体,reflect.Value 是一个具体值的包装。

反射让我们能够动态地访问对象的类型和数据,并根据需要对其进行操作。

常用类型

reflect.Type

reflect.Type 是对 Go 类型的描述。可以通过它获取有关类型的信息,比如类型名、类型的种类、是否是指针、结构体的字段等。

常见方法:

  • t.Kind():获取 reflect.Type 的底层类型(如 intstructslice 等)。
  • t.Name():获取类型的名称,仅对命名类型有效。
  • t.NumField():获取结构体类型的字段数。
  • t.Field(i):获取结构体的第 i 个字段。

reflect.Value

reflect.Value 代表一个变量的值,它包含了具体的值,可以通过它获取或修改数据。

常见方法:

  • v.Kind():获取 reflect.Value 的底层类型(如 intstructslice 等)。
  • v.Interface():将 reflect.Value 转换为 interface{} 类型。
  • v.Set():修改 reflect.Value 的值(需要是可修改的,即传入指针)。
  • v.Type():获取 reflect.Value 的类型。
  • v.String():获取 reflect.Value 的字符串表示。

常见的反射操作

获取类型和值

使用 reflect.TypeOf 获取类型,使用 reflect.ValueOf 获取值。

package mainimport ("fmt""reflect"
)func main() {var x int = 42// 获取类型t := reflect.TypeOf(x)// 获取值v := reflect.ValueOf(x)fmt.Println("Type:", t)     // 输出:Type: intfmt.Println("Value:", v)    // 输出:Value: 42
}

动态修改值

reflect 允许我们在运行时动态修改值。要修改值,必须传递指向变量的指针。

package mainimport ("fmt""reflect"
)func main() {var x int = 42p := reflect.ValueOf(&x) // 传入指针// 修改值p.Elem().SetInt(100)fmt.Println("Modified value:", x) // 输出:Modified value: 100
}

获取结构体字段

使用 reflect 获取结构体字段名和值。

package mainimport ("fmt""reflect"
)type Person struct {Name stringAge  int
}func printStructFields(s interface{}) {val := reflect.ValueOf(s)if val.Kind() == reflect.Struct {for i := 0; i < val.NumField(); i++ {field := val.Field(i)fmt.Printf("%s: %v\n", val.Type().Field(i).Name, field)}}
}func main() {p := Person{"Alice", 30}printStructFields(p)
}

使用反射调用方法

反射不仅可以获取类型和值,还能动态调用方法。

package mainimport ("fmt""reflect"
)type Person struct {Name string
}func (p *Person) SayHello() {fmt.Println("Hello, my name is", p.Name)
}func main() {p := &Person{Name: "Alice"}// 获取反射对象v := reflect.ValueOf(p)// 获取方法并调用method := v.MethodByName("SayHello")method.Call(nil)
}

反射与类型断言的对比

类型断言与反射在用途上有很大区别:

  • 类型断言:通常用于接口类型的断言,快速检查和转换接口类型为具体类型。
  • reflect:允许动态地操作类型和值,可以用于获取更多类型信息或修改值。

示例:类型断言

package mainimport "fmt"func printType(i interface{}) {if str, ok := i.(string); ok {fmt.Println("String:", str)} else if num, ok := i.(int); ok {fmt.Println("Integer:", num)} else {fmt.Println("Unknown type")}
}func main() {printType("Hello")printType(42)printType(3.14)
}

示例:使用 reflect 获取类型和值

package mainimport ("fmt""reflect"
)func main() {var x interface{} = 42v := reflect.ValueOf(x)t := reflect.TypeOf(x)fmt.Println("Type:", t) // 输出:Type: intfmt.Println("Value:", v) // 输出:Value: 42
}

总结:类型断言与反射对比

特性类型断言reflect 包
用途用于接口类型的类型转换用于动态类型检查、修改值、获取字段等
性能高效,编译时确定类型较慢,涉及运行时类型解析
语法简洁性简单直观语法较复杂
类型安全类型安全,编译时检查无类型安全,运行时可能出错
灵活性灵活性较低,仅适用于接口类型断言高度灵活,可动态修改、调用方法等

  • 案例
package _caseimport ("fmt""reflect"
)type student struct {Name string `json:"name,omitempty" db:"name2"`Age  int    `json:"age,omitempty"` // omitempty Zero-Value不序列化
}type User struct {Id   intName stringAge  int
}// 匿名字段
type Boy struct {UserAddr string
}func (u User) Hello(name string) {fmt.Println("hello", name)
}func ReflectCase1() {//reflectTest1()//reflectType("cz")//reflectValue(55.6)//reflectTest2()//u := User{1, "chen", 18}//Poni(u)//m := Boy{User{1, "sa", 20}, "bj"}//reflectTest3(m)//fmt.Println(u)//setValue(&u)//fmt.Println(u)//userMethod(u)//var s student//getTag(&s)
}func getTag(o any) {v := reflect.ValueOf(o)// 返回reflect.TypeOf类型t := v.Type()// 获取字段for i := 0; i < t.Elem().NumField(); i++ {f := t.Elem().Field(i)fmt.Print(f.Tag.Get("json"), "\t")fmt.Println(f.Tag.Get("db"))}
}func userMethod(o any) {v := reflect.ValueOf(o)// 获取方法m := v.MethodByName("Hello")// 有参数的话需要传一个Value类型切片args := []reflect.Value{reflect.ValueOf("666")}// 没有参数只需要:var args []reflect.Value// m.Call()m.Call(args)
}func setValue(o any) {v := reflect.ValueOf(o)// 获取指针指向的元素v = v.Elem()// 取字段f := v.FieldByName("Name")if f.Kind() == reflect.String {f.SetString("zhen")}
}func reflectTest3(o any) {t := reflect.TypeOf(o)fmt.Println(t)// Anoymous:匿名fmt.Printf("%#v\n", t.Field(0))// 值信息fmt.Printf("%#v\n", reflect.ValueOf(o).Field(0))
}func Poni(o any) {t := reflect.TypeOf(o)fmt.Println("类型:", t)fmt.Println("字符串类型:", t.Name())// 获取值v := reflect.ValueOf(o)fmt.Println(v)// 获取所有属性for i := 0; i < t.NumField(); i++ {f := t.Field(i)fmt.Printf("%s : %v, ", f.Name, f.Type)// 获取字段值信息val := v.Field(i).Interface()fmt.Println("val:", val)}fmt.Println("==method==")for i := 0; i < t.NumMethod(); i++ {m := t.Method(i)fmt.Println(m.Name)fmt.Println(m.Type)}
}// 在处理处理少量已知类型时,使用类型断言+switch性能更好,reflect性能低
// 相较于使用interface{} + switch + 类型推断处理结构体时无法获取详细的字段或标签信息。
// reflect处理复杂结构体内的字段,具有优势可以获取结构体的字段、标签、方法等详细信息。
// reflect使用场景:处理大量动态、未知的复杂数据类型,且这些类型在编译时无法预知,使用 reflect 可以在运行时获取这些类型信息
// 实现通用代码
func reflectTest2() {stu := student{Name: "chenzhen",Age:  19,}v := reflect.ValueOf(stu)// 获取struct字段数量fmt.Println("NumFields:", v.NumField())// 获取字段Name值:// 1.v.Field(指定字段序号) -> 适用于不知道字段名(或者结合for遍历操作)// 2.v.FieldByName("指定字段名") -> 适用于知道字段名fmt.Println("Name value:", v.Field(0).String(), ", ", v.FieldByName("Name").String())// 字段类型fmt.Println("Name type:", v.Field(0).Type())t := reflect.TypeOf(stu)for i := 0; i < t.NumField(); i++ {// 获取字段名name := t.Field(i).Namefmt.Println("Field Name:", name)// 获取tagif fieldName, ok := t.FieldByName(name); ok {tag := fieldName.Tagfmt.Println("tag-", tag, ", ", "json:", tag.Get("json"), ", id", tag.Get("id"))}}
}func reflectTest1() {x := 1.2345fmt.Println("TypeOf==")// TypeOf()返回接口中保存值的类型t := reflect.TypeOf(x)fmt.Println("type:", t)fmt.Println("kind:", t.Kind())fmt.Println("ValueOf==")v := reflect.ValueOf(x)fmt.Println("value:", v)fmt.Println("type:", v.Type())fmt.Println("kind:", v.Kind())// Float传入一个Value类型值,返回一个float64类型fmt.Println("value:", v.Float())z := v.Interface() // Interface()返回一个any类型值fmt.Println(z)fmt.Printf("value is %g\n", z)x1 := []int{1, 2, 3}v1 := reflect.ValueOf(x1)fmt.Println("type:", v1.Type())fmt.Println("kind:", v1.Kind())x2 := map[string]string{"test1": "1", "test2": "2"}v2 := reflect.ValueOf(x2)fmt.Println("type:", v2.Type())fmt.Println("kind:", v2.Kind())fmt.Println("kind==")// Kind()返回类型种类,与Type()区别为:如下案例,Kind返回更底层type MyInt intm := MyInt(5)v3 := reflect.ValueOf(m)fmt.Println("type:", v3.Type())fmt.Println("kind:", v3.Kind())
}func reflectType(a any) {t := reflect.TypeOf(a)fmt.Println("类型是:", t)// kind()获取具体类型k := t.Kind()fmt.Println(k)switch k {case reflect.Float64:fmt.Println("a is float64")case reflect.String:fmt.Println("string")default:panic("unhandled default case")}
}func reflectValue(a any) {v := reflect.ValueOf(a)fmt.Println(v)fmt.Println(v.Type())switch k := v.Kind(); k {case reflect.Float64:fmt.Println("a is ", v.Float())default:panic("unhandled default case")}
}

package _caseimport ("errors""fmt""reflect"
)func ReflectCase2() {type user struct {ID    int64Name  stringHobby []string}type outUser struct {ID    int64Name  stringHobby []string}u := user{ID: 1, Name: "nick", Hobby: []string{"篮球", "羽毛球"}}out := outUser{}// 需求1:使用reflect动态copy structrs := copy(&out, u)fmt.Println(rs, out)// 需求2:sliceUser := []user{{ID: 1, Name: "nick", Hobby: []string{"篮球", "羽毛球"}},{ID: 2, Name: "nick1", Hobby: []string{"篮球1", "羽毛球1"}},{ID: 3, Name: "nick2", Hobby: []string{"篮球2", "羽毛球2"}},}slice := sliceColumn(sliceUser, "Hobby")fmt.Println(slice)
}// 从一个切片或结构体中提取指定字段(colu)的值,并返回一个包含这些值的切片
// 每次 t = t.Elem() 或 v = v.Elem() 都是为了处理某一层的指针解引用问题,以便获取实际的值或类型。
// 如果传入的切片类型涉及指针,例如 *[]*Struct,就需要多次解引用才能得到实际的元素类型和值。// 对于四次t = t.Elem()解释
// reflect.Elem(),顾名思义,是取得变量的元素部分
// 在Golang中,变量的元素部分指的是指针指向的变量本身。
// 第一个 t = t.Elem() 处理传入 slice 是指针的情况。
// 第二个 t = t.Elem() 获取切片元素的类型。
// 第三个 t = t.Elem() 处理切片元素是指针的情况,获取指针指向的实际类型。
// o.Elem() 处理遍历时元素是指针的情况,解引用以访问字段。// 我的理解:对于
//
//	 if t.Kind() == reflect.Ptr {
//			t = t.Elem()
//			v = v.Elem()
//		}
//		第一个t = t.Elem()这是为了处理传入时传入的是切片地址的情况,如果传入的 slice 不是指针,比如 []Struct,这一段代码不会执行,因此不会影响后面的逻辑。
//		而如果传入的是切片,则会在第二个t = t.Elem()生效,这是因为切片打印出来是指向其第一个元素的地址,我们要的是其值,
//		所以要t = t.Elem()而接下来的
//		if t.Kind() == reflect.Ptr {
//			t = t.Elem()
//		}则是为了应对其在切片内部还有一个切片指针的情况,需要获取其值而最后的:
//		if o.Kind() == reflect.Ptr {
//				v1 := o.Elem()
//				val := v1.FieldByName(colu)
//				s = reflect.Append(s, val)
//			}则是处理切片中的切片中的field中指针的情况。
func sliceColumn(slice any, colu string) any {t := reflect.TypeOf(slice)v := reflect.ValueOf(slice)// 因为这里传入一个切片,切片值为指向其第一个元素的地址,所以要elemif t.Kind() == reflect.Ptr {t = t.Elem()v = v.Elem()}// 如果直接传入的slice是一个结构体,那么直接返回要找的colu对应值if v.Kind() == reflect.Struct {val := v.FieldByName(colu)return val.Interface()}// 处理切片情况if v.Kind() != reflect.Slice {return nil}t = t.Elem()// 如果还是一个指针,要找value,我们期望他是一个structif t.Kind() == reflect.Ptr {t = t.Elem()}f, _ := t.FieldByName(colu)// 获取要找字段的类型sliceT := reflect.SliceOf(f.Type)// 根据类型创建切片s := reflect.MakeSlice(sliceT, 0, 0)for i := 0; i < v.Len(); i++ {// index(i)返回v持有值的第i个元素。如果v的Kind不是Array、Chan、Slice、String,或者i出界,会panico := v.Index(i)if o.Kind() == reflect.Struct {val := o.FieldByName(colu)s = reflect.Append(s, val)}if o.Kind() == reflect.Ptr {v1 := o.Elem()val := v1.FieldByName(colu)s = reflect.Append(s, val)}}return s.Interface()
}func copy(dest any, source any) error {// 对sorece的reflect处理sT := reflect.TypeOf(source)sV := reflect.ValueOf(source)// 但是如果source传入的是指针,那么还要多操作一次,获取它的值if sT.Kind() == reflect.Ptr {sT = sT.Elem()sV = sV.Elem()}// 对于dest的reflect处理dT := reflect.TypeOf(dest)dV := reflect.ValueOf(dest)// 因为dest要被修改,所以传入的一定是指针if dT.Kind() != reflect.Ptr {return errors.New("target对象必须为指针类型")}dT = dT.Elem()dV = dV.Elem()// source必须为struct或者struct指针if sV.Kind() != reflect.Struct {return errors.New("sorce必须为struct或者struct指针")}// dest必须为struct指针if dV.Kind() != reflect.Struct {return errors.New("dest对象必须为struct指针")}// New()返回一个Value类型值,该值持有一个指向类型为传入类型的新申请的零值的指针,返回值的Type为PtrTo(typ)// 这里destObj是待复制对象,所以new出zero-valuedestObj := reflect.New(dT)for i := 0; i < dT.NumField(); i++ {// 每字段dField := dT.Field(i)if sField, ok := sT.FieldByName(dField.Name); ok {if dField.Type != sField.Type {continue}// 取sV中与dField.Name同名的Value赋给valuevalue := sV.FieldByName(dField.Name)// 设置destObj(指针)对应dField.Name的字段的值为valuedestObj.Elem().FieldByName(dField.Name).Set(value)}}dV.Set(destObj.Elem())// error nilreturn nil
}

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

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

相关文章

跟着AI 学 AI, 开发一个ChatBot, 集成 Json 数据和查询

按照规律&#xff0c;使用AI生成一个架构图 直接上代码&#xff0c;为了方便学习&#xff0c;直接按照如下方式&#xff0c;复制到你的开发环境即可调试&#xff0c;运行代码。做学习参考。 代码注释多次说明这里&#xff0c;不在赘述。 "type": "carousel&qu…

WPF+MVVM案例实战与特效(三十七)- 实现带有水印和圆角的自定义 TextBox 控件

文章目录 1、概述2、案例实现1、基本功能2、代码实现3、控件应用4、案例效果4、总结1、概述 在开发用户界面时,TextBox 是最常见的输入控件之一。为了提升用户体验,我们经常需要为 TextBox 添加一些额外的功能,例如显示提示文本(水印)和设置圆角边框。本文将详细介绍如何…

使用枚举实现单例模式,不会反序列化破坏攻击,不会被反射破坏攻击。(附带枚举单例的简单实现)

原因分析 1.反序列化方法 ① jdk8中的Enum源码中对反序列化方法进行重写&#xff0c;抛出异常。 java.lang.Enum#readObject方法截图如下 ②java.io.ObjectInputStream#readObject 方法中的 readEnum 方法处理了枚举类型的反序列化&#xff0c;从而确保了枚举的单例特性。 …

Linux下的守护程序

启动流程 嵌入式设备下Linux的内核系统启动的流程并不复杂&#xff0c;从最早的父进程init开始&#xff0c;为创建各种服务进程&#xff1a;系统会从 inittab 文件中&#xff0c;读取每一行作为执行命令&#x1f447; # Note: BusyBox init doesnt support runlevels. The r…

2024第十六届蓝桥杯模拟赛(第二期)-Python

# 2024第十六届蓝桥杯模拟赛&#xff08;第二期&#xff09;-Python题解 # 自己改注释# -----------------------1------------------------ # def prime(x): # if x < 2: # return 0 # for i in range(2, int(x ** 0.5) 1): # if x % i 0: # …

MongoDB-副本集

一、什么是 MongoDB 副本集&#xff1f; 1.副本集的定义 MongoDB 的副本集&#xff08;Replica Set&#xff09;是一组 MongoDB 服务器实例&#xff0c;它们存储同一数据集的副本&#xff0c;确保数据的高可用性和可靠性。副本集中的每个节点都有相同的数据副本&#xff0c;但…

《数据结构》(408代码题)

2009 单链表&#xff08;双指针&#xff09; 分析&#xff1a;首先呢&#xff0c;给我们的数据结构是一个带有表头结点的单链表&#xff0c;也不允许我们改变链表的结构。链表的长度不是直接给出的啊&#xff0c;所以这个倒数也很棘手。那我们该如何解决这个“k”呢&#xff0c…

6.1 初探MapReduce

MapReduce是一种分布式计算框架&#xff0c;用于处理大规模数据集。其核心思想是“分而治之”&#xff0c;通过Map阶段将任务分解为多个简单任务并行处理&#xff0c;然后在Reduce阶段汇总结果。MapReduce编程模型包括Map和Reduce两个阶段&#xff0c;数据来源和结果存储通常在…

Cad c#.net 一键修改标注dimension中的文本内容

本例为给标注加前缀&#xff0c;也可定制其他形式&#xff0c;效果如下&#xff1a; public class Demo{[CommandMethod("xx")]//public void Dim(){Document doc Application.DocumentManager.MdiActiveDocument;Database db doc.Database;Editor ed doc.Editor;…

Scala的隐式类

package hfd //隐式类 //任务&#xff1a;给之前的BaseUser添加新的功能&#xff0c;但是不要直接去改代码 //思路&#xff1a;把BaseUser通过隐式转换&#xff0c;改成一个新类型&#xff0c;而这个新类型中有这新的方法 //implicit class一个隐式转换函数类 //作用&#xff1…

旅游系统旅游小程序PHP+Uniapp

旅游门票预订系统&#xff0c;支持景点门票、导游产品便捷预订、美食打卡、景点分享、旅游笔记分享等综合系统 更新日志 V1.3.0 1、修复富文本标签 2、新增景点入驻【高级版本】3、新增门票核销【高级版】4、新增门票端口【高级版】

MacOS系统 快速安装appium 步骤详解

在macOS系统上&#xff0c;你可以通过使用nvm&#xff08;Node Version Manager&#xff09;来管理Node.js的版本&#xff0c;并基于nvm安装的Node.js环境来快捷地安装Appium。以下是具体步骤&#xff1a; 一、安装nvm 下载nvm 访问nvm的GitHub仓库&#xff08;nvm GitHub&…

模型训练中梯度累积步数(gradient_accumulation_steps)的作用

模型训练中梯度累积步数&#xff08;gradient_accumulation_steps&#xff09;的作用 flyfish 在使用训练大模型时&#xff0c;TrainingArguments有一个参数梯度累积步数&#xff08;gradient_accumulation_steps&#xff09; from transformers import TrainingArguments梯…

技术速递|.NET 9 简介

作者&#xff1a;.NET 团队 排版&#xff1a;Alan Wang 今天&#xff0c;我们非常激动地宣布 .NET 9的发布&#xff0c;这是迄今为止最高效、最现代、最安全、最智能、性能最高的 .NET 版本。这是来自世界各地数千名开发人员又一年努力的成果。这个新版本包括数千项性能、安全和…

Vue项目打包部署到服务器

1. Vue项目打包部署到服务器 1.1. 配置 &#xff08;1&#xff09;修改package.json文件同级目录下的vue.config.js文件。 // vue.config.js module.exports {publicPath: ./, }&#xff08;2&#xff09;检查router下的index.js文件下配置的mode模式。   检查如果模式改…

【jpa】springboot使用jpa示例

目录 1. 请求示例2. pom依赖3. application.yaml4.controller5. service6. repository7. 实体8. 启动类 1. 请求示例 curl --location --request POST http://127.0.0.1:8080/user \ --header User-Agent: Apifox/1.0.0 (https://apifox.com) \ --header Content-Type: applic…

uniapp 常用的指令语句

uniapp 是一个使用 Vue.js 开发的跨平台应用框架&#xff0c;因此&#xff0c;它继承了 Vue.js 的大部分指令。以下是一些在 uniapp 中常用的 Vue 指令语句及其用途&#xff1a; v-if / v-else-if / v-else 条件渲染。v-if 有条件地渲染元素&#xff0c;v-else-if 和 v-else 用…

中企出海-德国会计准则和IFRS间的差异

根据提供的网页内容&#xff0c;德国的公认会计准则&#xff08;HGB&#xff09;与国际会计准则&#xff08;IFRS&#xff09;之间的主要差异可以从以下几个方面进行比较&#xff1a; 财务报告的目的&#xff1a; IFRS&#xff1a;财务报告主要是供投资者做决策使用&#xff0c…

NPU是什么?电脑NPU和CPU、GPU区别介绍

随着人工智能技术的飞速发展&#xff0c;计算机硬件架构也在不断演进以适应日益复杂的AI应用场景。其中&#xff0c;NPU&#xff08;Neural Processing Unit&#xff0c;神经网络处理器&#xff09;作为一种专为深度学习和神经网络运算设计的新型处理器&#xff0c;正逐渐崭露头…

使用skywalking,grafana实现从请求跟踪、 指标收集和日志记录的完整信息记录

Skywalking是由国内开源爱好者吴晟开源并提交到Apache孵化器的开源项目&#xff0c; 2017年12月SkyWalking成为Apache国内首个个人孵化项目&#xff0c; 2019年4月17日SkyWalking从Apache基金会的孵化器毕业成为顶级项目&#xff0c; 目前SkyWalking支持Java、 .Net、 Node.js、…