首頁  >  文章  >  後端開發  >  go語言中反射三定律是什麼

go語言中反射三定律是什麼

青灯夜游
青灯夜游原創
2023-01-18 16:29:451674瀏覽

反射三定律:1、反射可以將“介面類型變數”轉換為“反射類型物件”,這裡反射類型指“reflect.Type”和“reflect.Value”;2、反射可以將“反射類型物件」轉換為「介面類型變數」;3、如果要修改「反射類型物件」其值必須是「可寫的」。

go語言中反射三定律是什麼

本教學操作環境:windows7系統、GO 1.18版本、Dell G3電腦。

在反射的世界裡,我們擁有了取得一個物件的型別,屬性及方法的能力。

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编程时光

【相关推荐:Go视频教程编程教学

以上是go語言中反射三定律是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn