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 如何用反射reflect操作结构体
Apr 28 Golang
Go使用协程交替打印字符
Apr 29 Golang
golang DNS服务器的简单实现操作
Apr 30 Golang
go select编译期的优化处理逻辑使用场景分析
Jun 28 Golang
Go语言实现一个简单的并发聊天室的项目实战
Mar 18 Golang
详解Golang如何优雅的终止一个服务
Mar 21 Golang
golang生成vcf通讯录格式文件详情
Mar 25 Golang
golang为什么要统一错误处理
Apr 03 Golang
golang生成并解析JSON
Apr 14 Golang
Golang 入门 之url 包
May 04 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
MySQL修改密码方法总结
2008/03/25 PHP
php获取url字符串截取路径的文件名和扩展名的函数
2010/01/22 PHP
如何利用PHP执行.SQL文件
2013/07/05 PHP
WordPress中用于获取文章信息以及分类链接的函数用法
2015/12/18 PHP
Zend Framework动作助手Json用法实例分析
2016/03/05 PHP
php简单的上传类分享
2016/05/15 PHP
ubutu 16.04环境下,PHP与mysql数据库,网页登录验证实例讲解
2017/07/20 PHP
PHP中register_shutdown_function函数的基础介绍与用法详解
2017/11/28 PHP
jquery实现多屏多图焦点图切换特效的方法
2015/05/04 Javascript
常用的Javascript设计模式小结
2015/12/09 Javascript
jQuery+CSS实现简单切换菜单示例
2016/07/27 Javascript
AngularJs定制样式插入到ueditor中的问题小结
2016/08/01 Javascript
Angular限制input框输入金额(是小数的话只保留两位小数点)
2017/07/13 Javascript
Vue路由对象属性 .meta $route.matched详解
2019/11/04 Javascript
vue-calendar-component 封装多日期选择组件的实例代码
2020/12/04 Vue.js
详解javascript脚本何时会被执行
2021/02/05 Javascript
Python处理RSS、ATOM模块FEEDPARSER介绍
2015/02/18 Python
Django ORM框架的定时任务如何使用详解
2017/10/19 Python
python3连接MySQL数据库实例详解
2018/05/24 Python
python range()函数取反序遍历sequence的方法
2018/06/25 Python
Python3将数据保存为txt文件的方法
2019/09/12 Python
Pytorch 保存模型生成图片方式
2020/01/10 Python
Tensorflow分批量读取数据教程
2020/02/07 Python
Tensorflow 定义变量,函数,数值计算等名字的更新方式
2020/02/10 Python
MAC彩妆英国官网:M·A·C UK
2018/05/30 全球购物
Nixon手表英国官网:美国尼克松手表品牌
2020/02/10 全球购物
师范应届生教师求职信
2013/11/05 职场文书
公司业务主管岗位职责
2013/12/07 职场文书
三下乡活动方案
2014/01/31 职场文书
四个太阳教学反思
2014/02/01 职场文书
新闻发布会主持词
2014/03/28 职场文书
高职教师先进事迹材料
2014/08/24 职场文书
陈斌强事迹观后感
2015/06/17 职场文书
围城读书笔记
2015/06/26 职场文书
2015年教导处教学工作总结
2015/07/22 职场文书
使用 CSS 构建强大且酷炫的粒子动画效果
2022/08/14 HTML / CSS