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语言中json数据的读取和写出操作
Apr 28 Golang
Golang 空map和未初始化map的注意事项说明
Apr 29 Golang
Golang 如何实现函数的任意类型传参
Apr 29 Golang
golang slice元素去重操作
Apr 30 Golang
goland设置颜色和字体的操作
May 05 Golang
goland 设置project gopath的操作
May 06 Golang
关于golang高并发的实现与注意事项说明
May 08 Golang
Golang实现AES对称加密的过程详解
May 20 Golang
go 实现简易端口扫描的示例
May 22 Golang
Go归并排序算法的实现方法
Apr 06 Golang
golang生成并解析JSON
Apr 14 Golang
GoFrame基于性能测试得知grpool使用场景
Jun 21 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
input file获得文件根目录简单实现
2013/04/26 PHP
php使用GD创建保持宽高比缩略图的方法
2015/04/17 PHP
javascript multibox 全选
2009/03/22 Javascript
JS定时器实例
2013/04/17 Javascript
js动态生成指定行数的表格
2013/07/11 Javascript
jquery中的过滤操作详细解析
2013/12/02 Javascript
解析Javascript中大括号“{}”的多义性
2013/12/02 Javascript
css结合js制作下拉菜单示例代码
2014/02/27 Javascript
JS给超链接加确认对话框的方法
2015/02/24 Javascript
jQuery中页面返回顶部的方法总结
2016/12/30 Javascript
JavaScript制作简易计算器(不用eval)
2017/02/05 Javascript
jQuery实现的简单获取索引功能示例
2018/06/04 jQuery
Vue项目引进ElementUI组件的方法
2018/11/11 Javascript
vue.js中ref和$refs的使用及示例讲解
2019/08/14 Javascript
layerui代码控制tab选项卡,添加,关闭的实例
2019/09/04 Javascript
js 下拉菜单点击旁边收起实现(踩坑记)
2019/09/29 Javascript
Vue.js实现大屏数字滚动翻转效果
2019/11/29 Javascript
在Vue中创建可重用的 Transition的方法
2020/06/02 Javascript
Vue路由 重定向和别名的区别说明
2020/09/09 Javascript
vc6编写python扩展的方法分享
2014/01/17 Python
Python实现的科学计算器功能示例
2017/08/04 Python
Python实现统计英文文章词频的方法分析
2019/01/28 Python
如何在python中实现随机选择
2019/11/02 Python
Python处理PDF与CDF实例
2020/02/26 Python
使用CSS3美化HTML表单的技巧演示
2016/05/17 HTML / CSS
使用canvas一步步实现图片打码功能的方法
2019/06/17 HTML / CSS
使用Html5中的cavas画一面国旗
2019/09/25 HTML / CSS
Kenneth Cole官网:纽约时尚优雅品牌
2016/11/14 全球购物
美国户外生活方式品牌:Eddie Bauer
2016/12/28 全球购物
Famous Footwear加拿大:美国多品牌运动休闲鞋店
2018/12/05 全球购物
Bed Bath & Beyond加拿大官网:购买床上用品、浴巾、厨房电器等
2019/10/04 全球购物
啤酒销售实习自我鉴定
2013/09/24 职场文书
学校出纳员岗位职责
2014/03/18 职场文书
2015年计划生育责任书
2015/05/08 职场文书
大学校园餐饮创业计划书
2019/08/07 职场文书
UNION CREATIVE《Re:从零开始的异世界生活》雷姆手办
2022/03/20 日漫