ホームページ >バックエンド開発 >Golang >Go 言語におけるリフレクションの 3 つの法則とは何ですか

Go 言語におけるリフレクションの 3 つの法則とは何ですか

青灯夜游
青灯夜游オリジナル
2023-01-18 16:29:451783ブラウズ

リフレクションの 3 つの法則: 1. リフレクションは、「インターフェイス タイプ変数」を「リフレクション タイプ オブジェクト」に変換できます (リフレクション タイプは、「reflect.Type」および「reflect.Value」を参照します)。 Convert " 「リフレクション型オブジェクト」を「インターフェース型変数」に変換します; 3. 「リフレクション型オブジェクト」を変更する場合は、その値が「書き込み可能」である必要があります。

Go 言語におけるリフレクションの 3 つの法則とは何ですか

このチュートリアルの動作環境: Windows 7 システム、GO バージョン 1.18、Dell G3 コンピューター。

リフレクションの世界では、オブジェクトの型、プロパティ、メソッドを取得できます。

Go 言語におけるリフレクションの 3 つの法則とは何ですか

2 つのタイプ: タイプと値

Go リフレクションの世界では、非常に重要な 2 つのタイプがあります。全体の基礎 リフレクションの核となるリフレクト パッケージの使用方法を学ぶときは、まず次の 2 つのタイプを学ぶ必要があります:

  • reflect.Type

  • reflect.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
}

の形式で存在します。同時に、多くのメソッドを使用できます (以下の表を参照)。スペースの制限があるため、ここでそれらを 1 つずつ紹介することはできません。

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

前のセクション () の内容から、インターフェイス変数が実際には 1 つのペア (型とデータ) で構成されていることがわかりました。このペアは、実際の変数の値と値を記録します。つまり、現実の世界では、型と値を組み合わせてインターフェイス変数を形成します。

リフレクションの世界では、型とデータは分離されており、それぞれreflect.Typeとreflect.Valueで表されます。

リフレクションの 3 つの法則の解釈

Go 言語にはリフレクションの 3 つの法則があり、リフレクションを学習する際に非常に重要なリファレンスになります。

  • リフレクションはインターフェイス値からリフレクション オブジェクトに移行します。

  • リフレクションはリフレクション オブジェクトからインターフェイス値に移行します。

  • リフレクション オブジェクトを変更するには、値が設定可能である必要があります。

翻訳すると、

  • リフレクションは「インターフェイス タイプ」を変換できます。変数」を「リフレクション型オブジェクト」に;

  • Reflection は「リフレクション型オブジェクト」を「インターフェース型変数」に変換できます;

  • 「リフレクション タイプ オブジェクト」を変更したいのですが、そのタイプは「書き込み可能」である必要があります。

#第一法則

リフレクションはインターフェイス値から取得されます。

#インターフェイス変数からリフレクション オブジェクトへの変換を実現するには、reflect パッケージ内の 2 つの非常に重要なメソッドについて言及する必要があります。

  • reflect.TypeOf(i): インターフェイス値の型を取得します

  • reflect.ValueOf(i): インターフェイス値の値を取得します

これら 2 つのメソッド 返されるオブジェクトは、リフレクション オブジェクト (Type オブジェクトと Value オブジェクト) と呼ばれます。

Go 言語におけるリフレクションの 3 つの法則とは何ですか

たとえば、これら 2 つのメソッドがどのように使用されるかを見てみましょう。

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 复制代码

これでインターフェース型変数からリフレクションオブジェクトへの変換が完了しました。

ちょっと待って、上で定義した年齢は int 型ではないでしょうか?なぜ最初のルールではインターフェイス型であると言われているのでしょうか?

この点については、実は前のセクション (インターフェースに関する 3 つの「隠れルール」) で述べたとおり、TypeOf と ValueOf の 2 つの関数は、インターフェース{ } 空のインターフェースを受け取ります。 type と Go 言語関数はすべて値によって渡されるため、Go 言語は暗黙的に型をインターフェイス型に変換します。

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

第 2 法則

リフレクションは、リフレクション オブジェクトからインターフェイス値まで進みます。

第 1 法則の逆です。第 2 法則は、反射されたオブジェクトからインターフェイス変数への変換を説明します。

Go 言語におけるリフレクションの 3 つの法則とは何ですか

ソース コードから、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 言語におけるリフレクションの 3 つの法則とは何ですか

第三定律

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 言語におけるリフレクションの 3 つの法則とは何ですか

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

来举个例子

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 言語におけるリフレクションの 3 つの法則とは何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。