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 25 Golang
Golang 实现获取当前函数名称和文件行号等操作
May 08 Golang
Golang中异常处理机制详解
Jun 08 Golang
浅谈Go语言多态的实现与interface使用
Jun 16 Golang
golang内置函数len的小技巧
Jul 25 Golang
Golang并发操作中常见的读写锁详析
Aug 30 Golang
Golang使用Panic与Recover进行错误捕获
Mar 22 Golang
Go并发4种方法简明讲解
Apr 06 Golang
Go语言安装并操作redis的go-redis库
Apr 14 Golang
Golang bufio详细讲解
Apr 21 Golang
Golang解析JSON对象
Apr 30 Golang
Golang 入门 之url 包
May 04 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
ThinkPHP框架获取最后一次执行SQL语句及变量调试简单操作示例
2018/06/13 PHP
Laravel 读取 config 下的数据方法
2019/10/13 PHP
ASP.NET jQuery 实例11 通过使用jQuery validation插件简单实现用户登录页面验证功能
2012/02/03 Javascript
浏览器打开层自动缓慢展开收缩实例代码
2013/07/04 Javascript
js调用打印机打印网页字体总是缩小一号的解决方法
2014/01/24 Javascript
Javascript 读取操作Sql中的Xml字段
2014/10/09 Javascript
nodejs教程之入门
2014/11/21 NodeJs
JavaScript动态修改背景颜色的方法
2015/04/16 Javascript
Jquery日期选择datepicker插件用法实例分析
2015/06/08 Javascript
如何使用PHP+jQuery+MySQL实现异步加载ECharts地图数据(附源码下载)
2016/02/23 Javascript
javascript函数的四种调用模式
2017/01/08 Javascript
利用vue.js插入dom节点的方法
2017/03/15 Javascript
react路由配置方式详解
2017/08/07 Javascript
详解微信小程序实现WebSocket心跳重连
2018/07/31 Javascript
Vue入门之数量加减运算操作示例
2018/12/11 Javascript
实例讲解JavaScript预编译流程
2019/01/24 Javascript
Centos7 安装Node.js10以上版本的方法步骤
2019/10/15 Javascript
vue 解决移动端弹出键盘导致页面fixed布局错乱的问题
2019/11/06 Javascript
Python中用startswith()函数判断字符串开头的教程
2015/04/07 Python
python如何实现反向迭代
2018/03/20 Python
python实现从pdf文件中提取文本,并自动翻译的方法
2018/11/28 Python
python对矩阵进行转置的2种处理方法
2019/07/17 Python
Django文件存储 默认存储系统解析
2019/08/02 Python
Django ORM 聚合查询和分组查询实现详解
2019/08/09 Python
Django 在iframe里跳转顶层url的例子
2019/08/21 Python
谈一谈数组拼接tf.concat()和np.concatenate()的区别
2020/02/07 Python
如何基于python3和Vue实现AES数据加密
2020/03/27 Python
html5使用canvas绘制太阳系效果
2014/12/15 HTML / CSS
HTML5标签大全
2016/11/23 HTML / CSS
喜诗官方在线巧克力店:See’s Candies
2017/01/01 全球购物
德国富尔达运动鞋店:43einhalb
2020/12/25 全球购物
车间班组长的职责
2013/12/13 职场文书
开学典礼演讲稿
2014/05/23 职场文书
大学生个人简历自我评价
2015/03/11 职场文书
在校证明模板
2015/06/17 职场文书
PostgreSQL自动更新时间戳实例代码
2021/11/27 PostgreSQL