go言語reflectメモ
reflect パッケージについてメモ
他記事:
reflectとは
- reflectはGoでReflection機能を提供するためのパッケージ
- このパッケージの関数を使うことで、引数の型や値を動的に取得できる
使い方1. (reflectで参照した変数の値を変更しないとき)
package main import ( "fmt" "reflect" ) type Point struct { X int Y int } func main() { p := Point{10, 5} // 変数を定義 rv := reflect.ValueOf(p) fmt.Printf("rv.Type = %v\n", rv.Type()) // 名前空間付きの型名 fmt.Printf("rv.Kind= %v\n", rv.Kind()) // 格納リソース種別 fmt.Printf("rv.Interface= %v\n", rv.Interface()) // interface{}としての実際の値 xv := rv.Field(0) // rv内のX要素を取り出し fmt.Printf("xv: %d\n", xv.Int()) // intに変換して表示 }
実行結果
rv.Type = main.Point rv.Kind= struct rv.Interface= {10 5} xv: 10
- 変数の値を変更しないのであれば下のように変数(p)を直接reflect.ValueOfに入れると、reflect.Valueが取得できる
Typeでreflect.Valueの名前空間付きの型の名前(例ではmain.Point)を取得できる
- https://golang.org/pkg/reflect/#Value.Type
Type returns v's type.
- https://golang.org/pkg/reflect/#Value.Type
Kindでint, struct, mapなどの型種別が取得できる
- https://golang.org/pkg/reflect/#Value.Kind
Kind returns v's Kind. If v is the zero Value (IsValid returns false), Kind returns Invalid.
- https://golang.org/pkg/reflect/#Value.Kind
Fieldでstructのフィールドを取得できる
- https://golang.org/pkg/reflect/#Value.Field
Field returns the i'th field of the struct v. It panics if v's Kind is not Struct or i is out of range.
- https://golang.org/pkg/reflect/#Value.Field
使い方2. (reflectで参照した変数の値を変更するとき)
- 変数の値を変更するときは、SetInt(SetFloat, SetStringなど他にもいろいろ)を使う
- ただし、変更するためには以下のようにValueOfにポインタを渡して、そのElemを取る必要がある
Elemはinterfaceの値もしくはポインタの参照先を返す
- reflect - The Go Programming Language
Elem returns the value that the interface v contains or that the pointer v points to. It panics if v's Kind is not Interface or Ptr. It returns the zero Value if v is nil.
- reflect - The Go Programming Language
package main import ( "fmt" "reflect" ) type Point struct { X int Y int } func main() { p := &Point{X: 10, Y: 5} // ポインタにすることによって編集可能にする rv := reflect.ValueOf(p).Elem() // Elemによってポインタが指している先の値を取得する fmt.Printf("rv.Type = %v\n", rv.Type()) // 名前空間付きの型名 fmt.Printf("rv.Kind= %v\n", rv.Kind()) // 格納リソース種別 fmt.Printf("rv.Interface= %v\n", rv.Interface()) // interface{}としての実際の値 xv := rv.Field(0) // rv内のX要素を取り出し fmt.Printf("xv: %d\n", xv.Int()) // intに変換して表示 xv.SetInt(100) // intの100をX要素にセット fmt.Printf("after SetInt xv: %d\n", xv.Int()) // intに変換して表示 }
実行結果
rv.Type = main.Point rv.Kind= struct rv.Interface= {10 5} xv: 10 after SetInt xv: 100
【値を変更しようとしてpanicになる場合1】ポインタを渡さなかった場合
- ポインタではなく値を変更しようとするとpanicになる
func main() { p := Point{X: 10, Y: 5} rv := reflect.ValueOf(p) xv := rv.Field(0) // rv内のX要素を取り出し fmt.Printf("xv: %d\n", xv.Int()) // intに変換して表示 xv.SetInt(100) // intの100をX要素にセット => panic: reflect: reflect.Value.SetInt using unaddressable value fmt.Printf("after SetInt xv: %d\n", xv.Int()) // intに変換して表示 }
実行結果
xv: 10 panic: reflect: reflect.Value.SetInt using unaddressable value goroutine 1 [running]: reflect.flag.mustBeAssignable(0x82, 0xf0a20) /usr/local/go/src/reflect/value.go:234 +0x180 reflect.Value.SetInt(0xf0460, 0x414020, 0x82, 0x7, 0x64, 0x0) /usr/local/go/src/reflect/value.go:1542 +0x40 main.main() /tmp/sandbox268672126/prog.go:18 +0x200
【値を変更しようとしてpanicになる場合2】Elemを使わなかった場合
- Elemでreflect.Valueの指し示す先の値を取得していないとFieldメソッドを使った時にpanicになる
- Field (再掲)
- reflect - The Go Programming Language
Field returns the i'th field of the struct v. It panics if v's Kind is not Struct or i is out of range.
- reflect - The Go Programming Language
func main() { p := &Point{X: 10, Y: 5} rv := reflect.ValueOf(p) xv := rv.Field(0) // rv内のX要素を取り出し => panic: reflect: call of reflect.Value.Field on ptr Value fmt.Printf("xv: %d\n", xv.Int()) // intに変換して表示 xv.SetInt(100) // intの100をX要素にセット fmt.Printf("after SetInt xv: %d\n", xv.Int()) // intに変換して表示 }
実行結果
panic: reflect: call of reflect.Value.Field on ptr Value goroutine 1 [running]: reflect.Value.Field(0xe83c0, 0x414020, 0x16, 0x0, 0x40c120, 0x0, 0x90b80, 0x38f1) /usr/local/go/src/reflect/value.go:813 +0x160 main.main() /tmp/sandbox389676022/prog.go:16 +0xe0
値を変更できるか(SetXXXを使えるか)はCanSetを使うと判定できる
package main import ( "fmt" "reflect" ) type Point struct { X int Y int } func main() { p := Point{X: 10, Y: 5} rv := reflect.ValueOf(p) if rv.CanSet() { fmt.Println("p CanSet: true") xv := rv.Field(0) // rv内のX要素を取り出し fmt.Printf("xv: %d\n", xv.Int()) // intに変換して表示 xv.SetInt(100) fmt.Printf("after SetInt xv: %d\n", xv.Int()) // intに変換して表示 } else { fmt.Println("p CanSet: false") } pPtr := &Point{X: 10, Y: 5} rv = reflect.ValueOf(pPtr).Elem() if rv.CanSet() { fmt.Println("pPtr CanSet: true") xv := rv.Field(0) // rv内のX要素を取り出し fmt.Printf("xv: %d\n", xv.Int()) // intに変換して表示 xv.SetInt(100) fmt.Printf("after SetInt xv: %d\n", xv.Int()) // intに変換して表示 } else { fmt.Println("pPtr CanSet: true") } }
実行結果
p CanSet: false pPtr CanSet: true xv: 10 after SetInt xv: 100
使えそうな使用例
1. 構造体のフィールドを順に出力
package main import ( "fmt" "reflect" ) type Point struct { X int Y int Z int } func (p Point) PrintFiled() { v := reflect.ValueOf(p) t := v.Type() for i := 0; i < t.NumField(); i++ { // 構造体のフィールド名、フィールドの値、フィールドの型を出力 fmt.Printf("%s %d %T\n", t.Field(i).Name, v.Field(i).Interface(), v.Field(i).Interface()) } } func main() { p1 := Point{X: 10, Y: 5} p1.PrintFiled() fmt.Println("----") p2 := Point{X: 100, Y: 50, Z: 7} p2.PrintFiled() }
実行結果
X 10 int Y 5 int Z 0 int ---- X 100 int Y 50 int Z 7 int
2. 構造体の型が何であってもinterface型のSliceにまとめて返す
- どんな型であってもinterfaceのスライスにしたい場面があったので
- 以下のように、toInterfaceSlice関数を定義して、PointとUserのInterfaceSliceメソッドの中でこの関数を呼ぶだけにすることで共通化できた
- toInterfaceSlice関数の引数はinterface{}なのでどの構造体でも受け付けられる
package main import ( "fmt" "reflect" ) func toInterfaceSlice(v interface{}) []interface{} { var vs []interface{} rv := reflect.ValueOf(v) rt := rv.Type() for i := 0; i < rt.NumField(); i++ { vs = append(vs, rv.Field(i).Interface()) } return vs } type Point struct { X int Y int Z int } func (p Point) InterfaceSlice() []interface{} { return toInterfaceSlice(p) } type User struct { ID int Name string Height float64 Weight float64 } func (u User) InterfaceSlice() []interface{} { return toInterfaceSlice(u) } func main() { p := Point{X: 10, Y: 5, Z: 1} for _, v := range p.InterfaceSlice() { fmt.Printf("%v %T\n", v, v) } fmt.Println("----") u := User{ID: 1001, Name: "hoge", Height: 170.1, Weight: 60.45} for _, v := range u.InterfaceSlice() { fmt.Printf("%v %T\n", v, v) } }
実行結果
10 int 5 int 1 int ---- 1001 int hoge string 170.1 float64 60.45 float64
2-2. 関数に渡すパラメータが構造体のポインタの場合でも対応できるようにする
- 上のtoInterfaceSlice関数で引数が構造体のポインタの場合には以下のようにElemを使って実体を取得するようにすれば、 引数が構造体の場合も、構造体のポインタの場合も両方対応できる
func toInterfaceSlice(v interface{}) []interface{} { var vs []interface{} rv := reflect.ValueOf(v) fmt.Printf("rv.Kind %v\n", rv.Kind()) // パラメータvが構造体のポインタのときはElemでポインタの指している先の値を取得する if rv.Kind() == reflect.Ptr { // vが構造体のポインタの時はここを通る rv = reflect.ValueOf(v).Elem() } rt := rv.Type() for i := 0; i < rt.NumField(); i++ { vs = append(vs, rv.Field(i).Interface()) } return vs } type Point struct { X int Y int Z int } func (p Point) InterfaceSlice() []interface{} { // toInterfaceSliceには構造体を渡す return toInterfaceSlice(p) } type User struct { ID int Name string Height float64 Weight float64 } func (u *User) InterfaceSlice() []interface{} { // toInterfaceSliceには構造体のポインタを渡す return toInterfaceSlice(u) }
全体のコードは以下
実行結果
rv.Kind struct 10 int 5 int 1 int ---- rv.Kind ptr 1001 int hoge string 170.1 float64 60.45 float64
2-3. 構造体のフィールドに構造体を含んでいる場合にも対応できるようにする
- 以下のように、構造体のフィールドに構造体を持つ場合(ネストしている場合)にも対応できるようにしたい
type GamePlayerInfo struct { User Point }
- 以下のように条件分岐して再帰を使ってみる
func toInterfaceSlice(v interface{}) []interface{} { var vs []interface{} rv := reflect.ValueOf(v) if rv.Kind() == reflect.Ptr { rv = reflect.ValueOf(v).Elem() } rt := rv.Type() for i := 0; i < rt.NumField(); i++ { if rv.Field(i).Kind() == reflect.Struct { // フィールドがstructの場合は再帰でinterfaceのSliceを取得して後ろにつなげる sl := toInterfaceSlice(rv.Field(i).Interface()) vs = append(vs, sl...) } else { vs = append(vs, rv.Field(i).Interface()) } } return vs }
全体のコードは以下
実行結果
10 int 5 int 1 int ---- 1001 int hoge string 170.1 float64 60.45 float64 ---- 1001 int hoge string 170.1 float64 60.45 float64 10 int 5 int 1 int
2-4. 構造体のフィールドで〇〇メソッドを持つ場合はそれを使うようにする(おまけ)
- 以下のように、Attribute変数(Enum)のように、Stringメソッドを持つ場合はそれで出力させる
- MethodByName を使う
type Attribute int const ( Fire Attribute = iota Water Earth Air ) // constのString変換メソッド func (a Attribute) String() string { return []string{"Fire", "Water", "Earth", "Air"}[a] } type SpecialGamePlayerInfo struct { User Point Attribute }
参考
Go言語におけるinterface{}とリフレクションを使ったパターン - Qiita
reflect-examples/README.md at master · a8m/reflect-examples · GitHub