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 相关文章推荐
Go语言中的UTF-8实现
Apr 26 Golang
golang通过递归遍历生成树状结构的操作
Apr 28 Golang
golang 如何用反射reflect操作结构体
Apr 28 Golang
golang 接口嵌套实现复用的操作
Apr 29 Golang
解决go在函数退出后子协程的退出问题
Apr 30 Golang
使用golang编写一个并发工作队列
May 08 Golang
入门学习Go的基本语法
Jul 07 Golang
深入理解go缓存库freecache的使用
Feb 15 Golang
Go语言实现一个简单的并发聊天室的项目实战
Mar 18 Golang
golang用type-switch判断interface的实际存储类型
Apr 14 Golang
golang定时器
Apr 14 Golang
Go 内联优化让程序员爱不释手
Jun 21 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
js 小数取整的函数
2010/05/10 Javascript
jquery的Theme和Theme Switcher使用小结
2010/09/08 Javascript
JQuery获取当前屏幕的高度宽度的实现代码
2011/07/12 Javascript
JQuery设置和去除disabled属性的5种方法总结
2013/05/16 Javascript
jQuery的attr与prop使用介绍
2013/10/10 Javascript
js中的eventType事件及其浏览器支持性介绍
2013/11/29 Javascript
javascript数组去重方法汇总
2015/04/23 Javascript
对于jQuery性能的一些优化建议
2015/08/13 Javascript
实例详解JavaScript获取链接参数的方法
2016/01/01 Javascript
js实现文本上下来回滚动
2017/02/03 Javascript
Vuejs实现购物车功能
2017/11/05 Javascript
js中bool值的转换及“&amp;&amp;”、“||”、 “!!”详解
2017/12/21 Javascript
动态加载权限管理模块中的Vue组件
2018/01/16 Javascript
在JS循环中使用async/await的方法
2018/10/12 Javascript
JavaScript表格隔行变色和Tab标签页特效示例【附jQuery版】
2019/07/11 jQuery
Vue 根据条件判断van-tab的显示方式
2020/08/03 Javascript
python实现从一组颜色中找出与给定颜色最接近颜色的方法
2015/03/19 Python
Python实现将xml导入至excel
2015/11/20 Python
python 排序算法总结及实例详解
2016/09/28 Python
简单谈谈Python流程控制语句
2016/12/04 Python
Pyinstaller将py打包成exe的实例
2018/03/31 Python
基于MTCNN/TensorFlow实现人脸检测
2018/05/24 Python
使用python判断jpeg图片的完整性实例
2019/06/10 Python
pyqt5 实现工具栏文字图片同时显示
2019/06/13 Python
介绍一款python类型检查工具pyright(推荐)
2019/07/03 Python
Python模块汇总(常用第三方库)
2019/10/07 Python
Python 中如何实现参数化测试的方法示例
2019/12/10 Python
伦敦哈德森鞋:Hudson Shoes
2018/02/06 全球购物
教师岗位职责
2013/11/17 职场文书
一封普通求职者的求职信
2013/11/20 职场文书
团员个人的自我评价
2013/12/02 职场文书
小学教师师德师风演讲稿
2014/08/22 职场文书
2014年教师思想工作总结
2014/12/03 职场文书
继承公证书格式
2015/01/26 职场文书
Python Django搭建文件下载服务器的实现
2021/05/10 Python
详解Redis集群搭建的三种方式
2021/05/31 Redis