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 interface{}==nil 的几种坑及原理分析
Apr 24 Golang
golang在GRPC中设置client的超时时间
Apr 27 Golang
golang goroutine顺序输出方式
Apr 29 Golang
golang elasticsearch Client的使用详解
May 05 Golang
Go标准容器之Ring的使用说明
May 05 Golang
goland 设置project gopath的操作
May 06 Golang
Go timer如何调度
Jun 09 Golang
Golang的继承模拟实例
Jun 30 Golang
修改并编译golang源码的操作步骤
Jul 25 Golang
Golang 实现 WebSockets 之创建 WebSockets
Apr 24 Golang
Go Grpc Gateway兼容HTTP协议文档自动生成网关
Jun 16 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生成WAP页面
2006/10/09 PHP
PHP容易被忽略而出错陷阱 数字与字符串比较
2011/11/10 PHP
PHP实现冒泡排序的简单实例
2016/05/26 PHP
Thinkphp框架 表单自动验证登录注册 ajax自动验证登录注册
2016/12/27 PHP
CL vs ForZe BO5 第一场 2.13
2021/03/10 DOTA
将HTMLCollection/NodeList/伪数组转换成数组的实现方法
2011/06/20 Javascript
Js中获取frames中的元素示例代码
2013/07/30 Javascript
js 用CreateElement动态创建标签示例
2013/11/20 Javascript
javascript判断两个IP地址是否在同一个网段的实现思路
2013/12/13 Javascript
关闭浏览器窗口弹出提示框并且可以控制其失效
2014/04/15 Javascript
javascript实现选中复选框后相关输入框变灰不可用的方法
2015/08/11 Javascript
JavaScript的Backbone.js框架环境搭建及Hellow world示例
2016/05/07 Javascript
模仿password输入框的实现代码
2016/06/07 Javascript
jQuery如何获取动态添加的元素
2016/06/24 Javascript
javascript学习笔记_浅谈基础语法,类型,变量
2016/09/19 Javascript
js实现文字向上轮播功能
2017/01/13 Javascript
基于DOM节点删除之empty和remove的区别(详解)
2017/09/11 Javascript
ES6 javascript中class静态方法、属性与实例属性用法示例
2017/10/30 Javascript
JavaScript数据结构之栈实例用法
2019/01/18 Javascript
vue使用echarts画组织结构图
2021/02/06 Vue.js
python获取本机mac地址和ip地址的方法
2015/04/29 Python
破解安装Pycharm的方法
2018/10/19 Python
Python OpenCV之图片缩放的实现(cv2.resize)
2019/06/28 Python
wxpython多线程防假死与线程间传递消息实例详解
2019/12/13 Python
python随机模块random使用方法详解
2020/02/14 Python
中国综合网上购物商城:苏宁易购
2016/08/09 全球购物
捷克母婴用品购物网站:Feedo.cz
2020/12/28 全球购物
描述Cookie和Session的作用,区别和各自的应用范围,Session工作原理
2015/03/25 面试题
项目申报专员岗位职责
2014/07/09 职场文书
治安消防安全责任书
2014/07/23 职场文书
2014年十一国庆节爱国演讲稿
2014/09/23 职场文书
民政局副局长民主生活会个人整改措施
2014/10/04 职场文书
工商局所长四风自我剖析及整改措施
2014/10/26 职场文书
项目负责人岗位职责
2015/02/15 职场文书
2015年银行信贷员工作总结
2015/05/19 职场文书
跳高加油稿
2015/07/21 职场文书