ホームページ >バックエンド開発 >Golang >Golangのリフレクションを詳しく解説した記事

Golangのリフレクションを詳しく解説した記事

青灯夜游
青灯夜游転載
2022-12-14 20:26:346165ブラウズ

この記事では主に Golang でのリフレクションについて説明し、皆さんについて新たな理解を得ることができれば幸いです。

Golangのリフレクションを詳しく解説した記事

Go 言語を一定期間使用している人は多く、中には 1 ~ 2 年使用している人もいますが、反映についてはまだ曖昧です。 Go言語. 、私は自分の心にあまり自信がありません。 [関連する推奨事項: Go ビデオ チュートリアル プログラミング教育 ]

さらに、リフレクションはほとんど使用されません。もちろん、それ自体に問題はありません。当然のことながら、最もシンプルで、最も効率的で、スケーラブルで、パフォーマンスの良い方法で処理するのが最も良い方法です。高度な使用法を機械的にコピーする必要はありません。結局のところ、仕事は私たちの実験場ではありません。実際に行って実験することができます。自分で、 this リフレクションの遊び方を詳しく見てみましょう

この記事では次の 5 つの側面から説明します

  • リフレクションとは
    #リフレクションのルール
##ユースケースと柔軟なアプリケーション
  • ##リフレクションの原則
  • 概要
  • リフレクションとは何かを簡単に見てみる

簡単に言うと、リフレクションとは、次のことにアクセスする機能です。プログラムの実行中にプログラム自体を変更する

たとえば、プログラムの実行中に、プログラムのフィールド名とフィールド値を変更したり、プログラムにインターフェイスのアクセス情報を提供したりすることもできます。

これは Go 言語で提供されるメカニズムです。言語のパブリック ライブラリでの Reflect の使用については多くのことがわかります。

たとえば、一般的に使用される fmt パッケージでは、

一般的に使用される

json シリアル化と逆シリアル化、当然、先ほど述べた gorm ライブラリもリフレクションを使用します

しかし、なぜ一般的に「リフレクションはどうですか?」を使用するのでしょうか?

リフレクションの機能によると、当然のことですが、提供するインターフェイスでは受信データ型がわからないため、特定のデータ型はプログラムの実行中にのみわかります

しかし、コーディング時には、プログラムの実行時に渡された型が何であるかを検証し (json のシリアル化など)、この特定のデータを操作したいとも考えています。このとき、リフレクションの機能を使用する必要があります

リフレクションが使用されている場所では、

インターフェイス{}

が表示されるのは驚くことではありませんか?

受信データ型が何になるかわからないからこそ、インターフェースとして設計しました。{}。インターフェースの特徴や使い方がわからない場合は、過去の記事を確認してください。 . :

インターフェースについて注意すべき点は何ですか?{}
#インターフェースについて注意すべき点は何ですか?{}次
  • #まず反省のルールに注意してください

まず、反省の 3 つの重要な法則に注意してください。ルールを理解した後、私たちはルールに従います プレーする際には問題はありません

, ルールを知らずに常に条項を発動した場合にのみ、奇妙な問題が発生します

##反省することができますインターフェイスの変更に使用されます。 Type 変数はリフレクション タイプのオブジェクトに変換されます。

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

    実行時に変更するリフレクション タイプのオブジェクト。次に、このオブジェクトに対応する値が書き込み可能である必要があります。
  • 上記の 3 つのルールも比較的理解しやすいものです。前に説明した安全でないパッケージ内のポインタをまだ覚えていますか?
  • 一般的に使用されるデータ型をパッケージ内の指定されたデータ型 (安全でないパッケージやリフレクト パッケージなど) に変換し、パッケージ内のルールに従ってデータを変更します

まったく同等そのため、ベストを変更することで、さまざまな操作を行うことができます。

ユースケースに注意して柔軟に使用してください

一般的には、最初に基本的なアプリケーションを学び、次に、その原則を学び、なぜこの方法で使用できるのかを学び、ゆっくりとより深く理解していきます。

法則 1 については、インターフェース型変数をリフレクション型オブジェクトに変換します

実際 ここで説明したインターフェイス タイプの変数については、int、float、string、map、slice、struct などの任意のデータ タイプの変数を渡すことができます。

リフレクション タイプ オブジェクトは、ここでは、reflect リフレクション パッケージの reflect.Type および reflect.Value オブジェクトとして理解できます。これらは、で提供される TypeOf# を通じて使用できます。 ## および ValueOf 関数は

を取得します (

reflect.Type は実際にはインターフェースであり、実装する必要があるさまざまなインターフェースが含まれています)。 Type 関連情報

以下に示すように、

reflect.Type のすべてのメソッドが表示されます。その中には、

  • green があります。はすべてのデータです。型はすべて呼び出し可能です。
  • 赤いものは、呼び出し可能な関数型のデータです。
    Black
  • は、Map、配列 Array、チャネル Chan、ポインタ Ptr、またはスライス Slice です。
    青色の
  • は、実行できる # です。
##Yellow
    はチャネル タイプの呼び出し

reflect.Value

実際 上記は構造体です。この構造体に従って、データ型と特定のデータを格納するメソッドのセットも関連付けられています。データ構造を見るとわかります

type Value struct {
   typ *rtype
   ptr unsafe.Pointer
   flag
}
参照unsafe.Pointer はこちら ご存知ですか? 最下位層は自然に unsafe.Pointer

uintptr に変換し、そのデータを変更してから元に戻すことができます。 、次の記事を確認できます:

#GO のポインタ?
  • 変数のデータ型と値を簡単に取得するための簡単なデモを作成します
  • func main() {   var demoStr string = "now reflect"
       fmt.Println("type:", reflect.TypeOf(demoStr))
       fmt.Println("value:", reflect.ValueOf(demoStr))
    }

法律の場合 2 番目、リフレクション型オブジェクトをインターフェイス型変数に変換します

reflect であるため、

reflect.Value

型を特定のデータ型に変換できます。対応する # があります。 ##typ *rtype および ptr unsafe.Pointer in Value . たとえば、reflect.Value オブジェクトのインターフェイスを渡すことができます。 () に対処するメソッド

#

func main() {   var demoStr string = "now reflect"
   fmt.Println("type:", reflect.TypeOf(demoStr))
   fmt.Println("value:", reflect.ValueOf(demoStr))   var res string
   res = reflect.ValueOf(demoStr).Interface().(string)
   fmt.Println("res == ",res)
}

法則 3 については、リフレクション タイプのオブジェクトを変更します

最初に、デモ コードでは、

TypeOf

ValueOf

で渡される変数は実際にはコピーです。リフレクション型オブジェクトでその値を変更したい場合は、そのアドレスを取得する必要があります。 この変数は書き込み可能であることが前提です 例を挙げると理解できます

func main() {
   var demoStr string = "now reflect"
   v := reflect.ValueOf(demoStr)
   fmt.Println("is canset ", v.CanSet())
   //v.SetString("hello world")   // 会panic
   }

最初に

reflect.Value オブジェクトの

CanSet

を呼び出して、書き込み可能かどうかを確認できます。書き込み可能であれば、再度書き込みます。書き込み可能でない場合は、書き込みを行わないでください。そうしないとパニックになります

では、渡された変数のアドレスは変更できるのでしょうか? ?

#アドレスを渡すという考えには何も問題はありませんが、値の設定方法に問題があるため、上記のパニック状況が発生します。

reflect.Value で特定のデータ ポインタを見つける必要があります。そうすれば、それを変更できます。

reflect.Value

Elem

メソッドもう少し複雑なもの

上記のケースを見てみればそう感じるかもしれません 簡単なケースはデモするまでは大丈夫ですが、仕事で使うとすぐにクラッシュしてしまいます 当然まだ完全には理解されていません。うまく消化されていません。これは仕事からの別の例です。

#A 構造体にはマップが含まれており、マップ内のキーは文字列で、値は []文字列## です。

#要件は、構造内の趣味フィールドに対応するマップ キーの ## 番目のスライス (

sport

##1
    要素) にアクセスし、それらを
  • に変更することです。 hellolworld
    type RDemo struct {
       Name  string
       Age   int
       Money float32
       Hobby map[string][]string
    }
    
    func main() {
       tmp := &RDemo{
          Name:  "xiaomiong",
          Age:   18,
          Money: 25.6,
          Hobby: map[string][]string{
             "sport": {"basketball", "football"},
             "food":  {"beef"},
          },
       }
    
       v := reflect.ValueOf(tmp).Elem()  // 拿到结构体对象
       h := v.FieldByName("Hobby")    // 拿到 Hobby 对象
       h1 := h.MapKeys()[0]    // 拿到 Hobby 的第 0 个key
       fmt.Println("key1 name == ",h1.Interface().(string))
    
       sli := h.MapIndex(h1)    // 拿到 Hobby 的第 0 个key对应的对象
       str := sli.Index(1)      // 拿到切片的第 1 个对象
       fmt.Println(str.CanSet())
    
       str.SetString("helloworld")
       fmt.Println("tmp == ",tmp)
    }
  • 可以看到上述案例运行之后有时可以运行成功,有时会出现 panic 的情况,相信细心的 xdm 就可以看出来,是因为 map 中的 key 是 无序的导致的,此处也提醒一波,使用 map 的时候要注意这一点

    看上述代码,是不是就能够明白咱们使用反射去找到对应的数据类型,然后按照数据类型进行处理数据的过程了呢

    有需要的话,可以慢慢的去熟练反射包中涉及的函数,重点是要了解其三个规则,对象转换方式,访问方式,以及数据修改方式

    反射原理

    那么通过上述案例,可以知道关于反射中数据类型和数据指针对应的值是相当重要的,不同的数据类型能够用哪些函数这个需要注意,否则用错直接就会 panic

    TypeOf

    来看 TypeOf 的接口中涉及的数据结构

    在 reflect 包中 rtype 是非常重要的,Go 中所有的类型都会包含这个结构,所以咱们反射可以应用起来,结构如下

    // rtype must be kept in sync with ../runtime/type.go:/^type._type.
    type rtype struct {
       size       uintptr
       ptrdata    uintptr
       hash       uint32 
       tflag      tflag
       align      uint8
       fieldAlign uint8
       kind       uint8
       equal     func(unsafe.Pointer, unsafe.Pointer) bool
       gcdata    *byte 
       str       nameOff
       ptrToThis typeOff
    }

    其中可以看到此处的 rtype 的结构保持和 runtime/type.go 一致 ,都是关于数据类型的表示,以及对应的指针,关于这一块的说明和演示可以查看文末的 interface{} 处的内容

    ValueOf

    ValueOf 的源码中,我们可以看到,重要的是 emptyInterface 结构

    // emptyInterface is the header for an interface{} value.type emptyInterface struct {
       typ  *rtype
       word unsafe.Pointer
    }复制代码

    emptyInterface 结构中有 rtype 类型的指针, word 自然是对应的数据的地址了

    reflect.Value 对象中的方法也是非常的多,用起来和上述说到的 reflect.Type 接口中的功能类似

    关于源码中涉及到的方法,就不再过多的赘述了,更多的还是需要自己多多实践才能体会的更好

    殊不知,此处的 reflect.Value 也是可以转换成 reflect.Type ,可以查看源码中 reflect\value.gofunc (v Value) Type() Type {

    其中   reflect.Value  ,reflect.Type ,和任意数据类型 可以相互这样来转换

    如下图:

    总结

    至此,关于反射就聊到这里,一些关于源码的细节并没有详细说,更多的站在一个使用者的角度去看反射需要注意的点

    关于反射,大多的人是建议少用,因为是会影响到性能,不过如果不太关注这一点,那么用起来还是非常方便的

    高级功能自然也是双刃剑,你用不好就会 panic,如果你期望去使用他,那么就去更多的深入了解和一步一步的吃透他吧

    大道至简,反射三定律,活学活用

    更多编程相关知识,请访问:编程视频!!

以上がGolangのリフレクションを詳しく解説した記事の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。