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 interface判断为空nil的实现代码
Apr 24 Golang
golang正则之命名分组方式
Apr 25 Golang
Go语言带缓冲的通道实现
Apr 26 Golang
golang中切片copy复制和等号复制的区别介绍
Apr 27 Golang
浅谈golang package中init方法的多处定义及运行顺序问题
May 06 Golang
golang中的并发和并行
May 08 Golang
Golang 获取文件md5校验的方法以及效率对比
May 08 Golang
Golang标准库syscall详解(什么是系统调用)
May 25 Golang
试了下Golang实现try catch的方法
Jul 01 Golang
如何利用golang运用mysql数据库
Mar 13 Golang
Golang Elasticsearches 批量修改查询及发送MQ
Apr 19 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
Blitz templates 最快的PHP模板引擎
2010/04/06 PHP
php注册登录系统简化版
2020/12/28 PHP
PHP标准类(stdclass)用法示例
2016/09/28 PHP
PHP+MySQL实现输入页码跳转到指定页面功能示例
2018/06/01 PHP
treepanel动态加载数据实现代码
2012/12/15 Javascript
图片Slider 带左右按钮的js示例
2013/08/30 Javascript
判断JS对象是否拥有某种属性的两种方式
2013/12/02 Javascript
jQuery(js)获取文字宽度(显示长度)示例代码
2013/12/31 Javascript
AngularJS基础知识笔记之表格
2015/05/10 Javascript
js实现浏览本地文件并显示扩展名的方法
2015/08/17 Javascript
图文详解Heap Sort堆排序算法及JavaScript的代码实现
2016/05/04 Javascript
Node.js的Web模板引擎ejs的入门使用教程
2016/06/06 Javascript
js返回顶部实例分享
2016/12/21 Javascript
JavaScript的六种继承方式(推荐)
2017/06/26 Javascript
JSON对象转化为字符串详解
2017/08/11 Javascript
微信小程序自定义带价格显示日历效果
2018/12/29 Javascript
CountUp.js数字滚动插件使用方法详解
2019/10/17 Javascript
JS实现数据动态渲染的竖向步骤条
2020/06/24 Javascript
[01:51]历届DOTA2国际邀请赛举办地回顾 TI9落地上海
2018/08/26 DOTA
Python中的Matplotlib模块入门教程
2015/04/15 Python
Python实现短网址ShortUrl的Hash运算实例讲解
2015/08/10 Python
Python cookbook(数据结构与算法)保存最后N个元素的方法
2018/02/13 Python
python 从文件夹抽取图片另存的方法
2018/12/04 Python
基于python 取余问题(%)详解
2020/06/03 Python
浅谈TensorFlow之稀疏张量表示
2020/06/30 Python
css3学习系列之移动属性详解
2017/07/04 HTML / CSS
伦敦一家非常流行的时尚精品店:Oxygen Boutique
2017/01/15 全球购物
餐饮商业计划书范文
2014/04/29 职场文书
2014年乡镇安全生产工作总结
2014/12/02 职场文书
单位接收函范文
2015/01/30 职场文书
初中生思想道德自我评价
2015/03/09 职场文书
中秋节祝酒词
2015/08/12 职场文书
Python pandas读取CSV文件的注意事项(适合新手)
2021/06/20 Python
vue+element ui实现锚点定位
2021/06/29 Vue.js
深入讲解数据库中Decimal类型的使用以及实现方法
2022/02/15 MySQL
java高级用法JNA强大的Memory和Pointer
2022/04/19 Java/Android