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语言操作数据库及其常规操作的示例代码
Apr 21 Golang
Go语言使用select{}阻塞main函数介绍
Apr 25 Golang
golang json数组拼接的实例
Apr 28 Golang
golang 接口嵌套实现复用的操作
Apr 29 Golang
golang goroutine顺序输出方式
Apr 29 Golang
Golang中interface{}转为数组的操作
Apr 30 Golang
解决golang post文件时Content-Type出现的问题
May 02 Golang
关于golang高并发的实现与注意事项说明
May 08 Golang
浅谈Golang 切片(slice)扩容机制的原理
Jun 09 Golang
golang操作redis的客户端包有多个比如redigo、go-redis
Apr 14 Golang
GO语言字符串处理函数之处理Strings包
Apr 14 Golang
Golang jwt身份认证
Apr 20 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写MySQL数据 实现代码
2009/06/15 PHP
php中导出数据到excel时数字变为科学计数的解决方法
2013/02/03 PHP
coreseek 搜索英文的问题详解
2013/06/08 PHP
慎用preg_replace危险的/e修饰符(一句话后门常用)
2013/06/19 PHP
PHP的foreach中使用引用时需要注意的一个问题和解决方法
2014/05/29 PHP
php下Memcached入门实例解析
2015/01/05 PHP
PHP简单预防sql注入的方法
2016/09/27 PHP
PHP对象的浅复制与深复制的实例详解
2017/10/26 PHP
PHP递归的三种常用方式
2019/02/28 PHP
php求斐波那契数的两种实现方式【递归与递推】
2019/09/09 PHP
laravel 多图上传及图片的存储例子
2019/10/14 PHP
js 深拷贝函数
2008/12/04 Javascript
JS 日期验证正则附asp日期格式化函数
2009/09/11 Javascript
仿百度输入框智能提示的js代码
2013/08/22 Javascript
跨域资源共享 CORS 详解
2016/04/26 Javascript
iScroll.js 使用方法参考
2016/05/16 Javascript
jQuery ajaxForm()的应用
2016/10/14 Javascript
js实现3D图片展示效果
2017/03/09 Javascript
node.js中实现kindEditor图片上传功能的方法教程
2017/04/26 Javascript
在Vue中使用echarts的实例代码(3种图)
2017/07/10 Javascript
详解微信小程序input标签正则初体验
2018/08/18 Javascript
JavaScript面试技巧之数组的一些不low操作
2019/03/22 Javascript
Jquery滑动门/tab切换实现方法完整示例
2020/06/05 jQuery
python利用标准库如何获取本地IP示例详解
2017/11/01 Python
tensorflow 获取变量&amp;打印权值的实例讲解
2018/06/14 Python
python+selenium实现QQ邮箱自动发送功能
2019/01/23 Python
python字符串Intern机制详解
2019/07/01 Python
基于python生成英文版词云图代码实例
2020/05/16 Python
通过代码实例了解Python异常本质
2020/09/16 Python
python中numpy.empty()函数实例讲解
2021/02/05 Python
万年牢教学反思
2014/02/15 职场文书
律师授权委托书范本
2014/10/07 职场文书
学院党的群众路线教育实践活动第一阶段情况汇报
2014/10/25 职场文书
小学国庆节活动总结
2015/03/23 职场文书
2017年寒假社区服务活动总结
2016/04/06 职场文书
导游词之江苏同里古镇
2019/11/18 职场文书