李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
GO
正文
27.Golang之反射三定律
Leefs
2022-07-22 AM
955℃
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
标签云
Spring
Kafka
nginx
Java编程思想
RSA加解密
Quartz
并发编程
线程池
SpringBoot
散列
Hive
Hadoop
FileBeat
MyBatis
机器学习
Flink
Jquery
Http
Netty
JavaScript
栈
FastDFS
Redis
Kibana
持有对象
Yarn
Stream流
前端
BurpSuite
Spark RDD
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭