Javaとかオブジェクト指向言語のリフレクションと同じ感覚で操作しようとすると結構ハマりますよね、golangのreflect。
特にポインタの場合、一味工夫が必要だったりするので、現場で使いそうな操作をまとめておきます。(これが更に "構造体の中のフィールド" だったりするとまた色々あるのですが、また後日にでも。)
package main
import (
"fmt"
"reflect"
"strconv"
)
func main() {
s := "Aerith"
print("************* ポインタを reflect パッケージで色々いじくってみる ***********")
ps := &s
print(*ps) // Aerith
//ps のreflect.Value構造体を取得
psValue := reflect.ValueOf(ps)
print(psValue.String()) // <*string Value>
//ps のreflect.Type構造体を取得
psType := reflect.TypeOf(ps)
print(psType.String()) // *string
//reflect.Type構造体をreflect.Value経由で取得
psTypeFromValue := psValue.Type()
print(psTypeFromValue.String()) // *string
//ポインタの型が *string かどうかを判定する
var stringPointer *string
typeOfStringPointer := reflect.TypeOf(stringPointer)
print(strconv.FormatBool(psType == typeOfStringPointer)) // true
//ps のreflect.Kind値を取得
psKind := psType.Kind()
print(psKind.String()) // ptr
//psがポインタであるかどうかのbool値を取得
psIsPointer := psKind == reflect.Ptr
print(strconv.FormatBool(psIsPointer)) // true
//ps のアドレス番号を取得
//(※ アドレス番号自体は実行するたびに変わったりします)
print(strconv.FormatUint(uint64(psValue.Pointer()), 16)) // c0000401f0
//逆に、reflect.Value構造体から新たなポインタを作成する
psCopy := psValue.Interface().(*string)
print(*psCopy) // Aerith
//同じアドレス c0000401f0 を指し示す入れ物 psCopy が増えただけ
psCopyValue := reflect.ValueOf(psCopy)
print(strconv.FormatUint(uint64(psCopyValue.Pointer()), 16)) // c0000401f0
//新しい入れ物 psCopy に違うアドレスを代入しても、コピー元の入れ物 ps には影響ない
s2 := "Barret"
psCopy = &s2
psCopyValue = reflect.ValueOf(psCopy)
print(*psCopy) // Barret
print(strconv.FormatUint(uint64(psCopyValue.Pointer()), 16)) // c0000402a0
print(*ps) // Aerith
print(strconv.FormatUint(uint64(psValue.Pointer()), 16)) // c0000401f0
print("********* ここからは、ポインタアドレスが指し示す先の値をダイレクトにいじくってみる *********")
//ポインタ ps が指し示す先の値のreflect.Value構造体を取得
psElemValue := psValue.Elem()
print(psElemValue.String()) // Aerith
//ポインタ ps が指し示す先の値のreflect.Type構造体を取得
psElemType := psElemValue.Type()
print(psElemType.String()) // string
//ポインタ ps が指し示す先の値のreflect.Kind値を取得
psElemKind := psElemType.Kind()
print(psElemKind.String()) // string
//ポインタ ps が指し示す先の値を書き換える
psElemValue.SetString("Cloud")
print(psElemValue.String()) // Cloud
// *ps がちゃんと書き換わっている
print(*ps) // Cloud
//大本の変数 s もちゃんと書き換わっている
print(s) // Cloud
//逆に、reflect.Value構造体から新たな変数を作成する
sCopyInterface := psElemValue.Interface()
sCopy := sCopyInterface.(string)
print(sCopy) // Cloud
//ポインタが指し示す先の値は同じでも、メモリ上のデータとしては別物になっている
sCopyPointer := &sCopy
sCopyValue := reflect.ValueOf(sCopyPointer)
print(strconv.FormatUint(uint64(sCopyValue.Pointer()), 16)) // c0000402c0
//ポインタ sCopyPointer が指し示す先の値を書き換える
sCopyValue.Elem().SetString("Tifa")
print(sCopyValue.Elem().String()) // Tifa
//大本の変数 sCopy もちゃんと書き換わっている
print(sCopy) // Tifa
//コピー元の変数 s は変わっていない
print(s) // Cloud
print("****************** ポインタがnilの場合 ******************")
//nilポインタが指し示す先の値を reflect で書き換えることはできない
sCopyPointer = nil
sCopyValue = reflect.ValueOf(sCopyPointer)
print(sCopyValue.String()) // <*string Value>
print(strconv.FormatBool(sCopyValue.CanSet())) // false
sCopyKind := sCopyValue.Kind()
print(sCopyKind.String()) // ptr
//ポインタが nil かどうかを判定する
print(strconv.FormatBool(sCopyValue.IsNil())) // true
//ポインタが指し示す先の値は <invalid Value>
sCopyElemValue := sCopyValue.Elem()
print(sCopyElemValue.String()) // <invalid Value>
sCopyElemKind := sCopyElemValue.Kind()
print(sCopyElemKind.String()) // invalid
//reflect.Type が取得不可能
sCopyElemValue.Type() // panic発生
/*
panic: reflect: call of reflect.Value.Type on zero Value
goroutine 1 [running]:
reflect.Value.Type(0x0, 0x0, 0x0, 0x1, 0x1)
/go/src/reflect/value.go:1877 +0x16d
reflect_pointer.main()
/golang/workspace/awesomeProject/main/reflect_pointer.go:110 +0x1ce2
*/
}
func print(s interface{}) {
fmt.Println(s)
}