李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
GO
正文
27.Golang之反射三定律
Leefs
2022-07-22 AM
1017℃
0条
[TOC] ### 前言 ![27.Golang之反射三定律01.jpeg](https://lilinchao.com/usr/uploads/2022/07/4070743963.jpeg) ### 一、概述 一个接口变量,实际上都是由一 `pair` 对(type 和 data)组合而成,pair 对中记录着实际变量的值和类型。也就是说在真实世界(反射前环境)里,type 和 value 是合并在一起组成接口变量的。 而在反射的世界(反射后的环境)里,type 和 data 却是分开的,他们分别由 `reflect.Type` 和 `reflect.Value` 来表现。 ### 二、反射第一定律 > 反射可以将“接口类型变量”转换为“反射类型对象”。 *注:这里反射类型指 reflect.Type 和 reflect.Value。* 通过之前我们讲过的 `reflect.TypeOf()` 方法和 `reflect.ValueOf()` 方法可以分别获得接口值的类型和接口值的值。这两个方法返回的对象,我们称之为反射对象。 ![27.Golang之反射三定律02.png](https://lilinchao.com/usr/uploads/2022/07/1080954473.png) **示例** ```go package main import ( "fmt" "reflect" ) func main() { var a interface{} = 10.12 fmt.Printf("接口变量的类型为 %T ,值为 %v\n", a, a) //接口变量的类型为 float64 ,值为 10.12 t := reflect.TypeOf(a) v := reflect.ValueOf(a) // 反射第一定律 fmt.Printf("从接口变量到反射对象:Type对象类型为 %T\n", t) //从接口变量到反射对象:Type对象类型为 *reflect.rtype fmt.Printf("从接口变量到反射对象:Value对象类型为 %T\n", v) //从接口变量到反射对象:Value对象类型为 reflect.Value } ``` ##### 源码分析 + **TypeOf()** ```go // TypeOf returns the reflection Type that represents the dynamic type of i. // If i is a nil interface value, TypeOf returns nil. func TypeOf(i interface{}) Type { eface := *(*emptyInterface)(unsafe.Pointer(&i)) return toType(eface.typ) } ``` + **ValueOf()** ```go // ValueOf returns a new Value initialized to the concrete value // stored in the interface i. ValueOf(nil) returns the zero Value. func ValueOf(i interface{}) Value { if i == nil { return Value{} } // TODO: Maybe allow contents of a Value to live on the stack. // For now we make the contents always escape to the heap. It // makes life easier in a few places (see chanrecv/mapassign // comment below). escapes(i) return unpackEface(i) } ``` 可以看到两个函数都是传递的一个空接口类型的值,参数为空接口时,可以接受任何类型。所以这里传入的类型可以是任何类型的值。 ### 三、反射第二定律 > 反射可以将“反射类型对象”转换为“接口类型变量” 第二定律刚好和第一定律相反,第一定律讲的是从接口变量到反射对象的转换,而第二定律讲的是从反射对象到接口变量的转换。 ![27.Golang之反射三定律03.png](https://lilinchao.com/usr/uploads/2022/07/2708932362.png) 一个 `reflect.Value` 类型的变量,我们可以使用 `Interface` 方法恢复其接口类型的值。事实上,这个方法会把 `type` 和 `value` 信息打包并填充到一个接口变量中,然后返回。 **其函数声明如下:** ```go // Interface returns v's current value as an interface{}. // It is equivalent to: // var i interface{} = (v's underlying value) // It panics if the Value was obtained by accessing // unexported struct fields. func (v Value) Interface() (i interface{}) { return valueInterface(v, true) } ``` 最后转换后的对象静态类型为 `interface{}`,可以使用类型断言转换为原始类型。 **示例** ```go package main import ( "fmt" "reflect" ) func main() { var a interface{} = 10.12 v := reflect.ValueOf(a) // 反射第二定律 i := v.Interface() fmt.Printf("从反射对象到接口变量:对象类型为 %T,值为 %v\n", i, i) //从反射对象到接口变量:对象类型为 float64,值为 10.12 // 使用类型断言进行转换 x := v.Interface().(float64) fmt.Printf("x 类型为 %T,值为 %v\n", x, x) //x 类型为 float64,值为 10.12 } ``` ### 四、反射第三定律 > 如果要修改“反射类型对象”其值必须是“可写的” 首先来看一看下面这段代码: ```go package main import "reflect" func main() { var a float64 = 10.12 v := reflect.ValueOf(a) v.SetFloat(20.1) } ``` **运行报如下错误** ``` panic: reflect: reflect.Value.SetFloat using unaddressable value ``` 这里你可能会疑惑,为什么这里会抛出寻址的异常,其实是因为这里的变量 `v` 是“不可写的”。`settable`(“可写性”)是反射类型变量的一个属性,但也不是说所有的反射类型变量都有这个属性。 要想知道一个 `reflect.Value` 类型变量的“可写性”,可以使用 `CanSet` 方法来进行检查: > CanSet() 可以确定一个 Value 是否可以修改 ```go package main import ( "fmt" "reflect" ) func main() { var a float64 = 10.12 v := reflect.ValueOf(a) //v.CanSet():检测变量是否可写 fmt.Println("是否可写:", v.CanSet()) //是否可写: false } ``` 从结果可以看到,这个变量 `v` 是不可写的。对于一个不可写的变量,使用 `Set` 方法会报错。 这里实质上还是 Go 语言里的函数都是值传递问题,想象一下这里传递给 `reflect.ValueOf` 函数的是变量 `a` 的一个拷贝,而非 `a` 本身,所以如果对反射对象进行更新,其原始变量 `a` 根本不会受到影响,所以是不合法的,**“可写性”就是为了避免这个问题而设计出来的**。 所以,要让反射对象具备“可写性”,一定要注意创建反射对象时要传入变量的指针。 **修改代码如下** ```go package main import ( "fmt" "reflect" ) func main() { var a float64 = 10.12 v := reflect.ValueOf(&a) fmt.Println("是否可写:", v.CanSet()) //是否可写: false } ``` 但运行该程序还是会输出不可写,因为事实上我们这里要修改的是该指针指向的数据,使用还要使用 `Value` 类型的 `Elem()` 方法,对指针进行“解引用”,该方法返回指针指向的数据。 ```go package main import ( "fmt" "reflect" ) func main() { var a float64 = 10.12 v := reflect.ValueOf(&a).Elem() fmt.Println("是否可写:", v.CanSet()) //是否可写: true v.SetFloat(20.1) fmt.Println(v) //20.1 } ``` *附参考原文链接* *http://www.go-edu.cn/2022/05/30/go-22-%E5%8F%8D%E5%B0%84/*
标签:
Golang基础
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2260.html
上一篇
26.Golang之反射介绍
下一篇
28.Golang读取文件
评论已关闭
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
4
标签云
Elastisearch
JVM
Spark
Golang基础
LeetCode刷题
并发编程
MySQL
Git
SQL练习题
栈
JavaWeb
Hbase
设计模式
Quartz
CentOS
Redis
Spring
MyBatisX
锁
SpringCloud
Kafka
GET和POST
前端
工具
并发线程
Shiro
高并发
Java编程思想
Tomcat
Http
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭