go语言中反射三定律指的是什么

反射三定律:1、反射可以将“接口类型变量”转换为“反射类型对象”,这里反射类型指“reflect.Type”和 “reflect.Value”;2、反射可以将“反射类型对象”转换为“接口类型变量”;3、如果要修改“反射类型对象”其值必须是“可写的”。

在反射的世界里,我们拥有了获取一个对象的类型,属性及方法的能力。

go语言中反射三定律指的是什么

两种类型:Type 和 Value

在 Go 反射的世界里,有两种类型非常重要,是整个反射的核心,在学习 reflect 包的使用时,先得学习下这两种类型:

  • reflect.Type

  • reflect.Value

它们分别对应着真实世界里的 type 和 value,只不过在反射对象里,它们拥有更多的内容。

从源码上来看,reflect.Type 是以一个接口的形式存在的

type Type interface {
    Align() int
    FieldAlign() int
    Method(int) Method
    MethodByName(string) (Method, bool)
    NumMethod() int
    Name() string
    PkgPath() string
    Size() uintptr
    String() string
    Kind() Kind
    Implements(u Type) bool
    AssignableTo(u Type) bool
    ConvertibleTo(u Type) bool
    Comparable() bool
    Bits() int
    ChanDir() ChanDir
    IsVariadic() bool
    Elem() Type
    Field(i int) StructField
    FieldByIndex(index []int) StructField
    FieldByName(name string) (StructField, bool)
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    In(i int) Type
    Key() Type
    Len() int
    NumField() int
    NumIn() int
    NumOut() int
    Out(i int) Type
    common() *rtype
    uncommon() *uncommonType
}

而 reflect.Value 是以一个结构体的形式存在,

type Value struct {
    typ *rtype
    ptr unsafe.Pointer
    flag
}

同时它接收了很多的方法(见下表),这里出于篇幅的限制这里也没办法一一介绍。

Addr
Bool
Bytes
runes
CanAddr
CanSet
Call
CallSlice
call
Cap
Close
Complex
Elem
Field
FieldByIndex
FieldByName
FieldByNameFunc
Float
Index
Int
CanInterface
Interface
InterfaceData
IsNil
IsValid
IsZero
Kind
Len
MapIndex
MapKeys
MapRange
Method
NumMethod
MethodByName
NumField
OverflowComplex
OverflowFloat
OverflowInt
OverflowUint
Pointer
Recv
recv
Send
send
Set
SetBool
SetBytes
setRunes
SetComplex
SetFloat
SetInt
SetLen
SetCap
SetMapIndex
SetUint
SetPointer
SetString
Slice
Slice3
String
TryRecv
TrySend
Type
Uint
UnsafeAddr
assignTo
Convert

通过上一节的内容(),我们知道了一个接口变量,实际上都是由一 pair 对(type 和 data)组合而成,pair 对中记录着实际变量的值和类型。也就是说在真实世界里,type 和 value 是合并在一起组成 接口变量的。

而在反射的世界里,type 和 data 却是分开的,他们分别由 reflect.Type 和 reflect.Value 来表现。

解读反射的三大定律

Go 语言里有个反射三定律,是你在学习反射时,很重要的参考:

  • Reflection goes from interface value to reflection object.

  • Reflection goes from reflection object to interface value.

  • To modify a reflection object, the value must be settable.

翻译一下,就是:

  • 反射可以将“接口类型变量” 转换为“反射类型对象”;

  • 反射可以将 “反射类型对象”转换为 “接口类型变量”;

  • 如果要修改 “反射类型对象” 其类型必须是 “可写的”;

第一定律

Reflection goes from interface value to reflection object.

为了实现从接口变量到反射对象的转换,需要提到 reflect 包里很重要的两个方法:

  • reflect.TypeOf(i) :获得接口值的类型

  • reflect.ValueOf(i):获得接口值的值

这两个方法返回的对象,我们称之为反射对象:Type object 和 Value object。

go语言中反射三定律指的是什么

举个例子,看下这两个方法是如何使用的?

package main

import (
"fmt"
"reflect"
)

func main() {
    var age interface{} = 25

    fmt.Printf("原始接口变量的类型为 %T,值为 %v \n", age, age)

    t := reflect.TypeOf(age)
    v := reflect.ValueOf(age)

    // 从接口变量到反射对象
    fmt.Printf("从接口变量到反射对象:Type对象的类型为 %T \n", t)
    fmt.Printf("从接口变量到反射对象:Value对象的类型为 %T \n", v)

}

输出如下

原始接口变量的类型为 int,值为 25 从接口变量到反射对象:Type对象的类型为 *reflect.rtype 
从接口变量到反射对象:Value对象的类型为 reflect.Value 复制代码

如此我们完成了从接口类型变量到反射对象的转换。

等等,上面我们定义的 age 不是 int 类型的吗?第一法则里怎么会说是接口类型的呢?

关于这点,其实在上一节(关于接口的三个 『潜规则』)已经提到过了,由于 TypeOf 和 ValueOf 两个函数接收的是 interface{} 空接口类型,而 Go 语言函数都是值传递,因此Go语言会将我们的类型隐式地转换成接口类型。

原始接口变量的类型为 int,值为 25 
从接口变量到反射对象:Type对象的类型为 *reflect.rtype 
从接口变量到反射对象:Value对象的类型为 reflect.Value

第二定律

Reflection goes from reflection object to interface value.

和第一定律刚好相反,第二定律描述的是,从反射对象到接口变量的转换。

go语言中反射三定律指的是什么

通过源码可知, reflect.Value 的结构体会接收 Interface 方法,返回了一个 interface{}  类型的变量(注意:只有 Value 才能逆向转换,而 Type 则不行,这也很容易理解,如果 Type 能逆向,那么逆向成什么呢?

// 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)
}

这个函数就是我们用来实现将反射对象转换成接口变量的一个桥梁。

例子如下

package main

import (
"fmt"
"reflect"
)

func main() {
    var age interface{} = 25

    fmt.Printf("原始接口变量的类型为 %T,值为 %v \n", age, age)

    t := reflect.TypeOf(age)
    v := reflect.ValueOf(age)

    // 从接口变量到反射对象
    fmt.Printf("从接口变量到反射对象:Type对象的类型为 %T \n", t)
    fmt.Printf("从接口变量到反射对象:Value对象的类型为 %T \n", v)

    // 从反射对象到接口变量
    i := v.Interface()
    fmt.Printf("从反射对象到接口变量:新对象的类型为 %T 值为 %v \n", i, i)

}

输出如下

原始接口变量的类型为 int,值为 25 
从接口变量到反射对象:Type对象的类型为 *reflect.rtype 
从接口变量到反射对象:Value对象的类型为 reflect.Value 
从反射对象到接口变量:新对象的类型为 int 值为 25

当然了,最后转换后的对象,静态类型为 interface{} ,如果要转成最初的原始类型,需要再类型断言转换一下,关于这点,我已经在上一节里讲解过了,你可以点此前往复习:()。

i := v.Interface().(int)

至此,我们已经学习了反射的两大定律,对这两个定律的理解,我画了一张图,你可以用下面这张图来加强理解,方便记忆。

go语言中反射三定律指的是什么

第三定律

To modify a reflection object, the value must be settable.

反射世界是真实世界的一个『映射』,是我的一个描述,但这并不严格,因为并不是你在反射世界里所做的事情都会还原到真实世界里。

第三定律引出了一个 settable (可设置性,或可写性)的概念。

其实早在以前的文章中,我们就一直在说,Go 语言里的函数都是值传递,只要你传递的不是变量的指针,你在函数内部对变量的修改是不会影响到原始的变量的。

回到反射上来,当你使用 reflect.Typeof 和 reflect.Valueof 的时候,如果传递的不是接口变量的指针,反射世界里的变量值始终将只是真实世界里的一个拷贝,你对该反射对象进行修改,并不能反映到真实世界里。

因此在反射的规则里

  • 不是接收变量指针创建的反射对象,是不具备『可写性』的

  • 是否具备『可写性』,可使用 CanSet() 来获取得知

  • 对不具备『可写性』的对象进行修改,是没有意义的,也认为是不合法的,因此会报错。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var name string = "Go编程时光"

    v := reflect.ValueOf(name)
    fmt.Println("可写性为:", v.CanSet())
}

输出如下

可写性为: false

要让反射对象具备可写性,需要注意两点

  • 创建反射对象时传入变量的指针

  • 使用 Elem()函数返回指针指向的数据

完整代码如下

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var name string = "Go编程时光"
    v1 := reflect.ValueOf(&name)
    fmt.Println("v1 可写性为:", v1.CanSet())

    v2 := v1.Elem()
    fmt.Println("v2 可写性为:", v2.CanSet())
}

输出如下

v1 可写性为: false
v2 可写性为: true

知道了如何使反射的世界里的对象具有可写性后,接下来是时候了解一下如何对修改更新它。

反射对象,都会有如下几个以 Set 单词开头的方法

go语言中反射三定律指的是什么

这些方法就是我们修改值的入口。

来举个例子

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var name string = "Go编程时光"
    fmt.Println("真实世界里 name 的原始值为:", name)

    v1 := reflect.ValueOf(&name)
    v2 := v1.Elem()

    v2.SetString("Python编程时光")
    fmt.Println("通过反射对象进行更新后,真实世界里 name 变为:", name)
}

输出如下

真实世界里 name 的原始值为: Go编程时光
通过反射对象进行更新后,真实世界里 name 变为: Python编程时光

原创文章,作者:WGGLO,如若转载,请注明出处:https://www.beidanyezhu.com/a/30533.html

(0)
WGGLO的头像WGGLO
上一篇 2025-02-19
下一篇 2025-02-19

相关推荐

  • go语言eof错误指的是什么

    在go语言中,eof是指文件结尾错误,是Go语言中最重要的错误变量,存在于io包中,用于表示输入流的结尾。因为每个文件都有一个结尾,所以“io.EOF”很多时候并不能算是一个错误,…

  • go语言控制反转指的是什么

    在go语言中,控制反转(IoC)是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度,就是代码控制权从业务代码“反转”到框架代码。常见的控制反转方式叫做依赖注入,还有…

    2025-02-19
  • go语言中不定参数指的是什么

    不定参数是指函数传入的参数个数为不定数量;不定参数声明使用“param …type”的语法格式。不定参数的特点:1、所有的不定参数类型必须是相同的;2、不定参数必须是函…

    2025-02-19
  • go语言依赖注入指的是什么

    在go语言中,依赖注入(DI)是一种解耦组件之间依赖关系的设计模式;在需要的时候,不同组件之间可以通过一个统一的界面获取其它组件中的对象和状态。依赖注入的好处是解耦;而解耦又能带来…

    2025-02-19
  • go语言中标识符指的是什么

    在go语言中,标识符是指Go语言对各种变量、方法、函数等命名时使用的字符序列;标识符由若干个字母、下划线“_”、和数字组成,且第一个字符必须是字母。通俗的讲就是凡可以自己定义的名称…

  • go语言结构体指的是什么

    在go语言,结构体是类型中带有成员的复合类型;它是一种聚合的数据类型,是由一系列具有相同类型或不同类型的数据构成的数据集合,每个数据称为结构体的成员。结构体成员是由一系列的成员变量…

    2025-02-18
  • go语言中切片和数组指的是什么

    在go语言中,数组是一个由固定长度的特定类型元素组成的序列,是同一种数据类型元素的集合,一个数组可以由零个或多个元素组成。和数组对应的类型是Slice(切片),切片是对数组的一个连…

    2025-02-18
  • go语言中str指的是什么

    在go语言中,str指的是“字符串”,是一个不可改变的字节序列。字符串可以包含任意的数据,但是通常是用来包含可读的文本,字符串是 UTF-8 字符的一个序列(当字符为 ASCII …

    2025-02-18
  • go语言中sql指的是什么

    SQL是指“结构化查询语言”,是一种操作数据库的语言,包括创建数据库、删除数据库、查询记录、修改记录、添加字段等。SQL是关系型数据库的标准语言,所有的关系型数据库管理系统(RDB…

  • go语言元组指的是什么

    元组是固定长度不可变的顺序容器(元素序列),go语言中没有元组类型,数组就相当于元组。在go语言中,数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成;…

    2025-02-18

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

分享本页
返回顶部