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
Go语言切片前或中间插入项与内置copy()函数详解
Apr 27 Golang
go类型转换及与C的类型转换方式
May 05 Golang
goland 设置project gopath的操作
May 06 Golang
go xorm框架的使用
May 22 Golang
一文搞懂Golang 时间和日期相关函数
Dec 06 Golang
Golang 并发下的问题定位及解决方案
Mar 16 Golang
victoriaMetrics库布隆过滤器初始化及使用详解
Apr 05 Golang
Golang流模式之grpc的四种数据流
Apr 13 Golang
GoFrame基于性能测试得知grpool使用场景
Jun 21 Golang
Go微服务项目配置文件的定义和读取示例详解
Jun 21 Golang
Go语言编译原理之源码调试
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
IIS+PHP+MySQL+Zend配置 (视频教程)
2006/12/13 PHP
PHP中使用foreach和引用导致程序BUG的问题介绍
2012/09/05 PHP
PHP实现变色验证码实例
2014/01/06 PHP
Yii2框架实现注册和登录教程
2016/09/30 PHP
浅谈laravel-admin form中的数据,在提交后,保存前,获取并进行编辑
2019/10/21 PHP
jQuery 1.0.2
2006/10/11 Javascript
asp javascript 实现关闭窗口时保存数据的办法
2007/11/24 Javascript
javascript控制frame,iframe的src属性代码
2009/12/31 Javascript
用JS判断IE版本的代码 超管用!
2011/08/09 Javascript
JavaScript中的匀速运动和变速(缓冲)运动详细介绍
2012/11/11 Javascript
jquery图片放大镜功能的实例代码
2013/03/26 Javascript
javascript新建标签,判断键盘输入,以及判断焦点(示例代码)
2013/11/25 Javascript
离开当前页面前使用js判断条件提示是否要离开页面
2014/05/02 Javascript
JavaScript中常见的八个陷阱总结
2017/06/28 Javascript
vuejs手把手教你写一个完整的购物车实例代码
2017/07/06 Javascript
js继承的这6种方式!(上)
2019/04/23 Javascript
JavaScript页面倒计时功能完整示例
2019/05/15 Javascript
python psutil库安装教程
2018/03/19 Python
numpy中loadtxt 的用法详解
2018/08/03 Python
Python实现对字典分别按键(key)和值(value)进行排序的方法分析
2018/12/19 Python
Python通过VGG16模型实现图像风格转换操作详解
2020/01/16 Python
如何解决cmd运行python提示不是内部命令
2020/07/01 Python
运行Python编写的程序方法实例
2020/10/21 Python
python 对一幅灰度图像进行直方图均衡化
2020/10/27 Python
用python查找统一局域网下ip对应的mac地址
2021/01/13 Python
CSS3 三维变形实现立体方块特效源码
2016/12/15 HTML / CSS
国际鲜花速递专家:Floraqueen
2016/11/24 全球购物
意大利奢侈品零售商:ilDuomo Novara
2019/09/11 全球购物
华为消费者德国官方网站:HUAWEI德国
2020/11/03 全球购物
《雨点儿》教学反思
2014/04/14 职场文书
关于环保的活动方案
2014/08/25 职场文书
2014客服代表实习自我鉴定
2014/09/18 职场文书
单独二胎证明
2015/06/24 职场文书
演讲比赛主持词
2015/06/29 职场文书
校园安全学习心得体会
2016/01/18 职场文书
《灰雀》教学反思
2016/02/19 职场文书