リフレクション
リフレクションは、実行時に言語自体の構造を検査するメカニズムで、柔軟に問題に対処できますが、同時にパフォーマンス問題などの欠点も明らかです。Go では、リフレクションは interface{} と密接に関連しており、interface{} が現れる場所には必ずリフレクションが存在します。Go のリフレクション API は標準ライブラリ reflect パッケージによって提供されます。
インターフェース
始める前に、runtime パッケージ下の 2 つのインターフェースを簡単に理解しましょう。Go では、インターフェースは本質的に構造体で、Go は実行時にインターフェースを 2 つの大きなカテゴリに分類します。1 つはメソッドセットを持たないインターフェース、もう 1 つはメソッドセットを持つインターフェースです。メソッドセットを持つインターフェースの場合、実行時には以下の構造体 iface によって表されます。
type iface struct {
tab *itab // データ型,インターフェース型,メソッドセットなどを含む
data unsafe.Pointer // 値へのポインタ
}メソッドセットを持たないインターフェースの場合、実行時には eface 構造体によって表されます。
type eface struct {
_type *_type // 型
data unsafe.Pointer // 値へのポインタ
}これら 2 つの構造体は reflect パッケージ下に対応する構造体タイプがあり、iface は nonEmptyInterface に対応します。
type nonEmptyInterface struct {
itab *struct {
ityp *rtype // 静的インターフェース型
typ *rtype // 動的具象型
hash uint32 // 型ハッシュ
_ [4]byte
fun [100000]unsafe.Pointer // メソッドセット
}
word unsafe.Pointer // 値へのポインタ
}eface は emptyInterface に対応します。
type emptyInterface struct {
typ *rtype // 動的具象型
word unsafe.Pointer // ポインタを指す値
}これら 2 つのタイプについて、公式は明確な定義を提供しています。
nonEmptyInterface: nonEmptyInterface is the header for an interface value with methodsemptyInterface: emptyInterface is the header for an interface{} value
上記で動的具象型という言葉に言及しましたが、原文は dynamic concrete type です。まず Go 言語は 100% の静的型付け言語で、静的という言葉は外部に表現される抽象的なインターフェース型が不変であることで、動的という言葉はインターフェースの内部に格納される具体的な実装型が変化可能であることを示しています。これで、インターフェースの簡単な原理については、後続のリフレクションの学習を満足させるのに十分です。
橋渡し
reflect パッケージ下には、Go の型を表す reflect.Type インターフェース型と、Go の値を表す reflect.Value 構造体型があります。
type Type interface {
...
Name() string
PkgPath() string
Size() uintptr
String() string
Kind() Kind
...
}
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}上記のコードでは多くの詳細を省略していますが、これら 2 つのタイプの存在を理解するだけで十分です。Go のすべてのリフレクション関連の操作はこれら 2 つのタイプに基づいており、reflect パッケージは Go の型を上記の 2 つの型に変換してリフレクション操作を行うための 2 つの関数を提供しています。それぞれ reflect.TypeOf 関数
func TypeOf(i any) Typeと reflect.ValueOf 関数
func ValueOf(i any) Value2 つの関数のパラメータ型はどちらも any で、interface{} のエイリアスです。リフレクション操作を実行したい場合は、まずその型を interface{} に変換する必要があります。これが前述の通り、リフレクションがあれば空インターフェースなしでは済まない理由です。厳密に言えば、空インターフェースは Go 型システムとリフレクションを接続する橋渡しです。以下の図はそのプロセスを形象的に表しています。

TIP
以下の文章では、便宜上、エイリアス any を使用して interface{} を置き換えます。
コア
Go には 3 つの古典的なリフレクションの法則があり、上記で説明した内容と組み合わせると理解しやすくなります。それぞれ以下の通りです。
リフレクションは
interface{}型変数をリフレクションオブジェクトに変換できます。リフレクションはリフレクションオブジェクトを
interface{}型変数に復元できます。リフレクションオブジェクトを変更するには、その値は設定可能でなければなりません。
これら 3 つの法則が Go リフレクションのコアです。型関連の情報をアクセスする必要がある場合は reflect.TypeOf を使用し、リフレクション値を変更する必要がある場合は reflect.ValueOf を使用します。
型
reflect.Type は Go の型を表し、reflect.TypeOf() 関数を使用して変数を reflect.Type に変換できます。コード例は以下の通りです。
func main() {
str := "hello world!"
reflectType := reflect.TypeOf(str)
fmt.Println(reflectType)
}出力結果
stringKind
Type に対して、Go 内部では reflect.Kind を使用して Go の基本型を表し、本質的には符号なし整数 uint です。
type Kind uintreflect パッケージは Kind を使用して Go のすべての基本型を列挙しています。
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Pointer
Slice
String
Struct
UnsafePointer
)Kind 型は Stringer インターフェースの String() メソッドのみを実装しており、このタイプにはこのメソッドのみがあります。String() メソッドの戻り値は内部の slice から取得されます。
var kindNames = []string{
Invalid: "invalid",
Bool: "bool",
Int: "int",
Int8: "int8",
Int16: "int16",
Int32: "int32",
Int64: "int64",
Uint: "uint",
Uint8: "uint8",
Uint16: "uint16",
Uint32: "uint32",
Uint64: "uint64",
Uintptr: "uintptr",
Float32: "float32",
Float64: "float64",
Complex64: "complex64",
Complex128: "complex128",
Array: "array",
Chan: "chan",
Func: "func",
Interface: "interface",
Map: "map",
Pointer: "ptr",
Slice: "slice",
String: "string",
Struct: "struct",
UnsafePointer: "unsafe.Pointer",
}type Type interface{
Kind() Kind
}Kind を通じて、空インターフェースに格納されている値がどの基本型であるかを知ることができます。
func main() {
// any 型の変数を宣言
var eface any
// 代入
eface = 100
// Kind メソッドを通じて型を取得
fmt.Println(reflect.TypeOf(eface).Kind())
}出力結果
intElem
type Type interface{
Elem() Type
}Type.Elem() メソッドを使用して、any 型のデータ構造が格納する要素型を判断できます。受け入れ可能な底辺パラメータ型はポインタ、スライス、配列、チャネル、マップのいずれかでなければならず、そうでない場合は panic します。以下のコード例です。
func main() {
var eface any
eface = map[string]int{}
rType := reflect.TypeOf(eface)
// key() は map のキーのリフレクション型を返す
fmt.Println(rType.Key().Kind())
fmt.Println(rType.Elem().Kind())
}出力
string
intポインタもコンテナと理解でき、ポインタに対して Elem() を使用すると、その指す要素のリフレクション型が取得されます。
func main() {
var eface any
// ポインタを代入
eface = new(strings.Builder)
rType := reflect.TypeOf(eface)
// ポインタが指す要素のリフレクション型を取得
vType := rType.Elem()
// パスパスを出力
fmt.Println(vType.PkgPath())
// 名前を出力
fmt.Println(vType.Name())
}strings
Builder配列、スライス、チャネルも同様に使用します。
Size
type Type interface{
Size() uintptr
}Size メソッドを通じて対応する型が占有するバイトサイズを取得できます。
func main() {
fmt.Println(reflect.TypeOf(0).Size())
fmt.Println(reflect.TypeOf("").Size())
fmt.Println(reflect.TypeOf(complex(0, 0)).Size())
fmt.Println(reflect.TypeOf(0.1).Size())
fmt.Println(reflect.TypeOf([]string{}).Size())
}出力結果
8
16
16
8
24TIP
unsafe.Sizeof() を使用しても同様の効果が得られます。
Comparable
type Type interface{
Comparable() bool
}Comparable メソッドを通じて、型が比較可能かどうかを判断できます。
func main() {
fmt.Println(reflect.TypeOf("hello world!").Comparable())
fmt.Println(reflect.TypeOf(1024).Comparable())
fmt.Println(reflect.TypeOf([]int{}).Comparable())
fmt.Println(reflect.TypeOf(struct{}{}).Comparable())
}出力
true
true
false
trueImplements
type Type interface{
Implements(u Type) bool
}Implements メソッドを通じて、型が特定のインターフェースを実装しているかどうかを判断できます。
type MyInterface interface {
My() string
}
type MyStruct struct {
}
func (m MyStruct) My() string {
return "my"
}
type HisStruct struct {
}
func (h HisStruct) String() string {
return "his"
}
func main() {
rIface := reflect.TypeOf(new(MyInterface)).Elem()
fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().Implements(rIface))
fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().Implements(rIface))
}出力結果
true
falseConvertibleTo
type Type interface{
ConvertibleTo(u Type) bool
}ConvertibleTo メソッドを使用して、型が別の指定された型に変換可能かどうかを判断できます。
type MyInterface interface {
My() string
}
type MyStruct struct {
}
func (m MyStruct) My() string {
return "my"
}
type HisStruct struct {
}
func (h HisStruct) String() string {
return "his"
}
func main() {
rIface := reflect.TypeOf(new(MyInterface)).Elem()
fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().ConvertibleTo(rIface))
fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().ConvertibleTo(rIface))
}出力
true
false値
reflect.Value はリフレクションインターフェースの値を表し、reflect.ValueOf() 関数を使用して変数を reflect.Value に変換できます。
func main() {
str := "hello world!"
reflectValue := reflect.ValueOf(str)
fmt.Println(reflectValue)
}出力結果
hello world!Type
func (v Value) Type() TypeType メソッドはリフレクション値の型を取得できます。
func main() {
num := 114514
rValue := reflect.ValueOf(num)
fmt.Println(rValue.Type())
}出力
intElem
func (v Value) Elem() Valueリフレクション値の要素リフレクション値を取得します。
func main() {
num := new(int)
*num = 114514
// ポインタを例に
rValue := reflect.ValueOf(num).Elem()
fmt.Println(rValue.Interface())
}出力
114514ポインタ
リフレクション値のポインタを取得する方法は 2 つあります。
// v のアドレスを表すポインタリフレクション値を返す
func (v Value) Addr() Value
// v の元の値への uintptr を返す。uintptr(Value.Addr().UnsafePointer()) と同等
func (v Value) UnsafeAddr() uintptr
// v の元の値への uintptr を返す
// v の Kind が Chan, Func, Map, Pointer, Slice, UnsafePointer の場合のみ使用可能、そうでない場合は panic
func (v Value) Pointer() uintptr
// v の元の値への unsafe.Pointer を返す
// v の Kind が Chan, Func, Map, Pointer, Slice, UnsafePointer の場合のみ使用可能、そうでない場合は panic
func (v Value) UnsafePointer() unsafe.Pointer例
func main() {
num := 1024
ele := reflect.ValueOf(&num).Elem()
fmt.Println("&num", &num)
fmt.Println("Addr", ele.Addr())
fmt.Println("UnsafeAddr", unsafe.Pointer(ele.UnsafeAddr()))
fmt.Println("Pointer", unsafe.Pointer(ele.Addr().Pointer()))
fmt.Println("UnsafePointer", ele.Addr().UnsafePointer())
}出力
&num 0xc0000a6058
Addr 0xc0000a6058
UnsafeAddr 0xc0000a6058
Pointer 0xc0000a6058
UnsafePointer 0xc0000a6058TIP
fmt.Println はパラメータの型をリフレクションで取得し、reflect.Value 型の場合、自動的に Value.Interface() を呼び出して元の値を取得します。
map で再度試します。
func main() {
dic := map[string]int{}
ele := reflect.ValueOf(&dic).Elem()
println(dic)
fmt.Println("Addr", ele.Addr())
fmt.Println("UnsafeAddr", *(*unsafe.Pointer)(unsafe.Pointer(ele.UnsafeAddr())))
fmt.Println("Pointer", unsafe.Pointer(ele.Pointer()))
fmt.Println("UnsafePointer", ele.UnsafePointer())
}出力
0xc00010e4b0
Addr &map[]
UnsafeAddr 0xc00010e4b0
Pointer 0xc00010e4b0
UnsafePointer 0xc00010e4b0値の設定
func (v Value) Set(x Value)リフレクションを通じてリフレクション値を変更する場合、その値はアドレス可能でなければなりません。この場合、要素の値を直接変更しようとするのではなく、ポインタを通じて要素の値を変更する必要があります。
func main() {
// *int
num := new(int)
*num = 114514
rValue := reflect.ValueOf(num)
// ポインタが指す要素を取得
ele := rValue.Elem()
fmt.Println(ele.Interface())
ele.SetInt(11)
fmt.Println(ele.Interface())
}出力
114514
11値の取得
func (v Value) Interface() (i any)Interface() メソッドを通じて、リフレクション値の元の値を取得できます。
func main() {
var str string
str = "hello"
rValue := reflect.ValueOf(str)
if v, ok := rValue.Interface().(string); ok {
fmt.Println(v)
}
}出力
hello関数
リフレクションを通じて関数のすべての情報を取得でき、リフレクションで関数を呼び出すこともできます。
情報
リフレクション型を通じて関数のすべての情報を取得します。
func Max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
rType := reflect.TypeOf(Max)
// 関数名を出力。リテラル関数の型には名前がありません
fmt.Println(rType.Name())
// パラメータと戻り値の数を出力
fmt.Println(rType.NumIn(), rType.NumOut())
rParamType := rType.In(0)
// 最初のパラメータの型を出力
fmt.Println(rParamType.Kind())
rResType := rType.Out(0)
// 最初の戻り値の型を出力
fmt.Println(rResType.Kind())
}出力
2 1
int
int呼び出し
リフレクション値を通じて関数を呼び出します。
func (v Value) Call(in []Value) []Valuefunc main() {
// 関数のリフレクション値を取得
rType := reflect.ValueOf(Max)
// パラメータ配列を渡す
rResValue := rType.Call([]reflect.Value{reflect.ValueOf(18), reflect.ValueOf(50)})
for _, value := range rResValue {
fmt.Println(value.Interface())
}
}出力
50構造体
以下の構造体があると仮定します。
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
money int
}
func (p Person) Talk(msg string) string {
return msg
}フィールドへのアクセス
reflect.StructField 構造の構造は以下の通りです。
type StructField struct {
// フィールド名
Name string
// パッケージ名
PkgPath string
// 型名
Type Type
// タグ
Tag StructTag
// フィールドのバイトオフセット
Offset uintptr
// インデックス
Index []int
// 埋め込みフィールドかどうか
Anonymous bool
}構造体フィールドにアクセスする方法は 2 つあり、1 つはインデックスを通じてアクセスし、もう 1 つは名前を通じてアクセスします。
type Type interface{
Field(i int) StructField
}インデックスを通じてアクセスする例
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// 構造体フィールドの数を出力
fmt.Println(rType.NumField())
for i := 0; i < rType.NumField(); i++ {
structField := rType.Field(i)
fmt.Println(structField.Index, structField.Name, structField.Type, structField.Offset, structField.IsExported())
}
}出力
4
[0] Name string 0 true
[1] Age int 16 true
[2] Address string 24 true
[3] money int 40 falsetype Type interface{
FieldByName(name string) (StructField, bool)
}名前を通じてアクセスする例
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// 構造体フィールドの数を出力
fmt.Println(rType.NumField())
if field, ok := rType.FieldByName("money"); ok {
fmt.Println(field.Name, field.Type, field.IsExported())
}
}出力
4
money int falseフィールドの修改
構造体フィールドの値を変更する場合は、構造体ポインタを渡す必要があります。以下はフィールドを変更する例です。
func main() {
// ポインタを渡す
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// フィールドを取得
name := rValue.FieldByName("Name")
// フィールドの値を変更
if (name != reflect.Value{}) { // reflect.Value{} が返された場合、そのフィールドが存在しないことを示す
name.SetString("jack")
}
// 構造体を出力
fmt.Println(rValue.Interface())
}出力
{jack 0 0}構造体のプライベートフィールドを変更するには、いくつかの追加操作が必要です。
func main() {
// ポインタを渡す
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// プライベートフィールドを取得
money := rValue.FieldByName("money")
// フィールドの値を変更
if (money != reflect.Value{}) {
// 構造体の未エクスポートフィールドへのポインタリフレクション値を構築
p := reflect.NewAt(money.Type(), money.Addr().UnsafePointer())
// そのポインタが指す要素、つまり変更するフィールドを取得
field := p.Elem()
// 値を変更
field.SetInt(164)
}
// 構造体を出力
fmt.Printf("%+v\n", rValue.Interface())
}Tag へのアクセス
StructField を取得した後、その Tag に直接アクセスできます。
// 存在しない場合、ok は false
func (tag StructTag) Lookup(key string) (value string, ok bool)
// 存在しない場合、空文字列を返す
func (tag StructTag) Get(key string) string例
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
name, ok := rType.FieldByName("Name")
if ok {
fmt.Println(name.Tag.Lookup("json"))
fmt.Println(name.Tag.Get("json"))
}
}出力
name true
nameメソッドへのアクセス
メソッドへのアクセスはフィールドへのアクセスプロセスと非常によく似ていますが、関数シグネチャは若干異なります。reflect.Method 構造体は以下の通りです。
type Method struct {
// メソッド名
Name string
// パッケージ名
PkgPath string
// メソッド型
Type Type
// メソッドに対応する関数。最初のパラメータはレシーバー
Func Value
// インデックス
Index int
}メソッド情報へのアクセス例
func main() {
// 構造体リフレクション型を取得
rType := reflect.TypeOf(new(Person)).Elem()
// メソッドの数を出力
fmt.Println(rType.NumMethod())
// メソッド情報を遍历して出力
for i := 0; i < rType.NumMethod(); i++ {
method := rType.Method(i)
fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
}
}出力
1
0 Talk func(main.Person, string) string trueメソッドのパラメータと戻り値の詳細を取得したい場合は、Method.Func を通じて取得できます。プロセスは関数情報へのアクセスと同じで、上記のコードを少し修正します。
func main() {
// 構造体リフレクション型を取得
rType := reflect.TypeOf(new(Person)).Elem()
// メソッドの数を出力
fmt.Println(rType.NumMethod())
// メソッド情報を遍历して出力
for i := 0; i < rType.NumMethod(); i++ {
method := rType.Method(i)
fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
fmt.Println("メソッドパラメータ")
for i := 0; i < method.Func.Type().NumIn(); i++ {
fmt.Println(method.Func.Type().In(i).String())
}
fmt.Println("メソッド戻り値")
for i := 0; i < method.Func.Type().NumOut(); i++ {
fmt.Println(method.Func.Type().Out(i).String())
}
}
}最初のパラメータが main.Person、つまりレシーバー型であることがわかります。
1
0 Talk func(main.Person, string) string true
メソッドパラメータ
main.Person
string
メソッド戻り値
stringメソッドの呼び出し
メソッドの呼び出しは関数の呼び出しプロセスと非常によく似ており、レシーバーを手動で渡す必要はありません。
func main() {
// 構造体リフレクション型を取得
rValue := reflect.ValueOf(new(Person)).Elem()
// メソッドの数を出力
fmt.Println(rValue.NumMethod())
// メソッド情報を遍历して出力
talk := rValue.MethodByName("Talk")
if (talk != reflect.Value{}) {
// メソッドを呼び出し、戻り値を取得
res := talk.Call([]reflect.Value{reflect.ValueOf("hello,reflect!")})
// 戻り値を遍历して出力
for _, re := range res {
fmt.Println(re.Interface())
}
}
}出力
1
hello,reflect!作成
リフレクションを通じて新しい値を構築できます。reflect パッケージは同時にいくつかの特殊な型に基づいて、より便利な関数を提供しています。
基本型
// リフレクション値へのポインタリフレクション値を返す
func New(typ Type) Valuestring を例に
func main() {
rValue := reflect.New(reflect.TypeOf(*new(string)))
rValue.Elem().SetString("hello world!")
fmt.Println(rValue.Elem().Interface())
}hello world!構造体
構造体の作成も reflect.New 関数を使用します。
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
money int
}
func (p Person) Talk(msg string) string {
return msg
}
func main() {
// 構造体リフレクション値を作成
rType := reflect.TypeOf(new(Person)).Elem()
person := reflect.New(rType).Elem()
fmt.Println(person.Interface())
}出力
{ 0 0}スライス
リフレクションでスライスを作成
func MakeSlice(typ Type, len, cap int) Valuefunc main() {
// スライスリフレクション値を作成
rValue := reflect.MakeSlice(reflect.TypeOf(*new([]int)), 10, 10)
// 遍历して値を設定
for i := 0; i < 10; i++ {
rValue.Index(i).SetInt(int64(i))
}
fmt.Println(rValue.Interface())
}[0 1 2 3 4 5 6 7 8 9]Map
リフレクションで Map を作成
func MakeMapWithSize(typ Type, n int) Valuefunc main() {
//map リフレクション値を構築
rValue := reflect.MakeMapWithSize(reflect.TypeOf(*new(map[string]int)), 10)
// 値を設定
rValue.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(1))
fmt.Println(rValue.Interface())
}map[a:1]チャネル
リフレクションでチャネルを作成
func MakeChan(typ Type, buffer int) Valuefunc main() {
// チャネルリフレクション値を作成
makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
fmt.Println(makeChan.Interface())
}関数
リフレクションで関数を作成
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Valuefunc main() {
// ラップ型と関数本体を渡す
fn := reflect.MakeFunc(reflect.TypeOf(new(func(int))).Elem(), func(args []reflect.Value) (results []reflect.Value) {
for _, arg := range args {
fmt.Println(arg.Interface())
}
return nil
})
fmt.Println(fn.Type())
fn.Call([]reflect.Value{reflect.ValueOf(1024)})
}出力
func(int)
1024完全等価
reflect.DeepEqual は、2 つの変数が完全に等しいかどうかを判断するためにリフレクションパッケージが提供する関数で、シグネチャは以下の通りです。
func DeepEqual(x, y any) boolこの関数は各基本型に対して処理を行っています。以下はいくつかの型の判断方法です。
- 配列:配列の各要素が完全に等しい
- スライス:どちらも
nilの場合、完全に等しいと判断されるか、どちらも空でない場合、長さ範囲内の要素が完全に等しい - 構造体:すべてのフィールドが完全に等しい
- マップ:どちらも
nilの場合、完全に等しい。どちらもnilでない場合、各キーがマップする値が完全に等しい - ポインタ:同じ要素を指すか、指す要素が完全に等しい
- インターフェース:インターフェースの具象型が完全に等しい場合
- 関数:どちらも
nilの場合のみ完全に等しく、そうでない場合は完全に等しくない
以下はいくつかの例です。
スライス
func main() {
a := make([]int, 100)
b := make([]int, 100)
fmt.Println(reflect.DeepEqual(a, b))
}出力
true構造体
func main() {
mike := Person{
Name: "mike",
Age: 39,
Father: nil,
}
jack := Person{
Name: "jack",
Age: 18,
Father: &mike,
}
tom := Person{
Name: "tom",
Age: 18,
Father: &mike,
}
fmt.Println(reflect.DeepEqual(mike, jack))
fmt.Println(reflect.DeepEqual(tom, jack))
fmt.Println(reflect.DeepEqual(jack, jack))
}出力
false
false
true