golang 如何用反射reflect操作结构体


Posted in Golang onApril 28, 2021

背景

需要遍历结构体的所有field

对于exported的field, 动态set这个field的value

对于unexported的field, 通过强行取址的方法来获取该值(tricky?)

思路

下面的代码实现了从一个strct ptr对一个包外结构体进行取值的操作,这种场合在笔者需要用到反射的场合中出现比较多

simpleStrtuctField 函数接受一个结构体指针,因为最后希望改变其值,所以传参必须是指针。然后解引用。

接下来遍历结构体的每个field, exported字段是CanInterface的,对于unexported字段,需要强行取址来获取其值

model.go

package model
type Person struct {
 Name string
 age  int
}
func NewPerson(name string, age int) *Person {
 return &Person{
  Name: name,
  age:  age,
 }
}

main.go

package main
import (
	"github.com/miaomiao3/log"
	"../model"
	"reflect"
	"unsafe"
)
func main() {
	person := model.NewPerson("haha", 12)
	log.Debug("before:%+v", person)
	simpleStrtuctField(person)
	simpleStrtuctField(person)
	log.Debug("after:%+v", person)
}
// get unexported field
func simpleStrtuctField(v interface{}) {
	dataType := reflect.TypeOf(v)
	dataValue := reflect.ValueOf(v)
	if dataType.Kind() == reflect.Ptr {
		if dataValue.IsNil() {
			panic("nil ptr")
		}
		// 如果是指针,则要判断一下是否为struct
		originType := reflect.ValueOf(v).Elem().Type()
		if originType.Kind() != reflect.Struct {
			return
		}
		// 解引用
		dataValue = dataValue.Elem()
		dataType = dataType.Elem()
	} else {
		panic("non ptr")
	}
	num := dataType.NumField()
	for i := 0; i < num; i++ {
		field := dataType.Field(i)
		fieldName := field.Name
		fieldValue := dataValue.FieldByName(fieldName)
		if !fieldValue.IsValid() {
			continue
		}
		if fieldValue.CanInterface() {
			log.Debug("exported fieldName:%v value:%v", fieldName, fieldValue.Interface())
			if fieldValue.CanSet() && fieldValue.Kind() == reflect.String {
				oldValue := fieldValue.Interface().(string)
				fieldValue.SetString(oldValue + " auto append")
			}
		} else {
			// 强行取址
			forceValue := reflect.NewAt(fieldValue.Type(), unsafe.Pointer(fieldValue.UnsafeAddr())).Elem()
			log.Debug("unexported fieldName:%v value:%v", fieldName, forceValue.Interface())
		}
	}
}

output:

2019/06/02 17:15:31.64 [D] before:&{Name:haha age:12}

2019/06/02 17:15:31.64 [D] exported fieldName:Name value:haha

2019/06/02 17:15:31.64 [D] unexported fieldName:age value:12

2019/06/02 17:15:31.64 [D] after:&{Name:haha auto append age:12}

可以看到,Name字段被反射改变了,age的值也已经获取到

补充:go语言通过反射创建结构体、赋值、并调用对应方法

看代码吧~

package main
import (
	"fmt"
	"reflect"
	"testing"
)
type Call struct {
	Num1 int
	Num2 int
}
func (call Call) GetSub(name string){
	fmt.Printf("%v 完成了减法运算,%v - %v = %v \n", name, call.Num1, call.Num2, call.Num1 - call.Num2)
}
func (call *Call) GetSum(name string){
	fmt.Printf("%v 完成了加法运算,%v + %v = %v \n", name, call.Num1, call.Num2, call.Num1 + call.Num2)
}
func TestReflect(t *testing.T) {
	var (
		call *Call
		rValues []reflect.Value
		rValues2 []reflect.Value
	)
	ptrType := reflect.TypeOf(call) //获取call的指针的reflect.Type
	trueType := ptrType.Elem() //获取type的真实类型
	ptrValue := reflect.New(trueType) //返回对象的指针对应的reflect.Value
	call = ptrValue.Interface().(*Call)
	trueValue := ptrValue.Elem() //获取真实的结构体类型
	trueValue.FieldByName("Num1").SetInt(123)//设置对象属性,注意这个一定要是真实的结构类型的reflect.Value才能调用,指针类型reflect.Value的会报错
	//ptrValue.FieldByName("Num2").SetInt(23)
	trueValue.FieldByName("Num2").SetInt(23)
	//rValues = make([]reflect.Value, 0)
	rValues = append(rValues, reflect.ValueOf("xiaopeng"))//调用对应的方法
	fmt.Println(rValues)
	trueValue.MethodByName("GetSub").Call(rValues)
	/*
	fixme 在反射中,指针的方法不可以给实际类型调用,实际类型的方法可以给指针类型调用,因为go语言对这种操作做了封装
	所以下面一句是没问题的
	下下一句会运行时报错
	 */
	//ptrValue.MethodByName("GetSub").Call(rValues)
	//trueValue.MethodByName("GetSum").Call(append(rValues2, reflect.ValueOf("hiram")))
	ptrValue.MethodByName("GetSum").Call(append(rValues2, reflect.ValueOf("hiram")))
	fmt.Println(call)
	
	/*
	fixme 在实际使用中  指针和实体都能相互转换,不会影响调用
	但是指针的方法在方法体内的操作会影响到结构体本身属性
	而实体的方法不会,因为go对于结构体、数组、基本类型都是值传递
	 */
	call.GetSub("aaa")
	(*call).GetSub("bbb")
	call.GetSum("ccc")
	(*call).GetSum("ddd")
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Golang 相关文章推荐
golang 实现菜单树的生成方式
Apr 28 Golang
对Golang中的FORM相关字段理解
May 02 Golang
Golang Gob编码(gob包的使用详解)
May 07 Golang
Golang全局变量加锁的问题解决
May 08 Golang
Golang实现AES对称加密的过程详解
May 20 Golang
Go 语言结构实例分析
Jul 04 Golang
Go Plugins插件的实现方式
Aug 07 Golang
golang语言指针操作
Apr 14 Golang
Golang jwt身份认证
Apr 20 Golang
Golang入门之计时器
May 04 Golang
深入理解 Golang 的字符串
May 04 Golang
GO中sync包自由控制并发示例详解
Aug 05 Golang
golang 生成对应的数据表struct定义操作
Apr 28 #Golang
golang 如何通过反射创建新对象
Apr 28 #Golang
golang 实现两个结构体复制字段
Apr 28 #Golang
go结构体嵌套的切片数组操作
Apr 28 #Golang
golang json数组拼接的实例
Apr 28 #Golang
golang 实现对Map进行键值自定义排序
Apr 28 #Golang
go语言中json数据的读取和写出操作
Apr 28 #Golang
You might like
PHP关于foreach复制知识点总结
2019/01/28 PHP
Thinkphp5框架实现获取数据库数据到视图的方法
2019/08/14 PHP
JavaScript 中的replace方法说明
2007/04/13 Javascript
jqPlot jquery的页面图表绘制工具
2009/07/25 Javascript
jQuery html()等方法介绍
2009/11/18 Javascript
JavaScript Chart 插件整理
2010/06/18 Javascript
javascript实现可改变滚动方向的无缝滚动实例
2013/06/17 Javascript
jquery固定底网站底部菜单效果
2013/08/13 Javascript
jquery判断输入密码两次是否相等
2020/04/22 Javascript
AngularJs学习第五篇从Controller控制器谈谈$scope作用域
2016/06/08 Javascript
使用js获取地址栏参数的方法推荐(超级简单)
2016/06/14 Javascript
Angular 如何使用第三方库的方法
2018/04/18 Javascript
webpack 代码分离优化快速指北
2019/05/18 Javascript
ES5 模拟 ES6 的 Symbol 实现私有成员功能示例
2020/05/06 Javascript
Angular+Ionic使用queryParams实现跳转页传值的方法
2020/09/05 Javascript
Python编程中的文件读写及相关的文件对象方法讲解
2016/01/19 Python
Python随机数random模块使用指南
2016/09/09 Python
python的变量与赋值详细分析
2017/11/08 Python
python OpenCV学习笔记实现二维直方图
2018/02/08 Python
修改python plot折线图的坐标轴刻度方法
2018/12/13 Python
pyqt5 实现多窗口跳转的方法
2019/06/19 Python
简单易懂Pytorch实战实例VGG深度网络
2019/08/27 Python
python多进程并发demo实例解析
2019/12/13 Python
Python如何基于rsa模块实现非对称加密与解密
2020/01/03 Python
解决python 在for循环并且pop数组的时候会跳过某些元素的问题
2020/12/11 Python
python3列表删除大量重复元素remove()方法的问题详解
2021/01/04 Python
战友聚会邀请函
2014/01/18 职场文书
城建学院毕业生自荐信
2014/01/31 职场文书
工作会议主持词
2014/03/17 职场文书
为自己工作观后感
2015/06/11 职场文书
无犯罪记录证明样本
2015/06/16 职场文书
公司考勤管理制度
2015/08/04 职场文书
小学班主任工作随笔
2015/08/15 职场文书
一起来学习Python的元组和列表
2022/03/13 Python
MySQL的表级锁,行级锁,排它锁和共享锁
2022/07/15 MySQL
如何使用注解方式实现 Redis 分布式锁
2022/07/23 Redis