324
214

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Go 言語 reflect チートシート

Last updated at Posted at 2016-09-27

はじめに

Go 言語の reflect パッケージは動的に型や値を扱いたい場合に便利です。

このページでは reflect パッケージの詳しい Example をチートシートとしてまとめました。

ご案内

このページの内容を別サイトにまとめなおしました。

目的別に項目を分けたのでやりたいことからコードを逆引きできます。

また、サイト上でコードを即時実行できるように The Go Playground の実行フォームを埋め込んだので、気軽にコードをお試しいただけます。

目次

チートシート

package main

import (
	"fmt"
	"reflect"
)

func main() {
	{
		p("/* 数値 */")

		p("// 型の取得")
		p(reflect.TypeOf(0)) //=> int

		p("// 型の種別の比較")
		// 比較対象の定義は https://golang.org/pkg/reflect/#Kind
		p(reflect.TypeOf(0).Kind() == reflect.Int) //=> true

		p("// 型の比較")
		p(reflect.TypeOf(0) == reflect.TypeOf(100)) //=> true

		p("// 変数へ reflect 経由で値をセットする")
		var i int
		// reflect.Value 型に変換
		// ポインタでないと値の変更ができないので &i で変数を渡して .Elem() でポインタの値を返している
		v := reflect.ValueOf(&i).Elem()
		// 値をセットできる
		p(v.CanSet()) //=> true
		// 整数値のセット
		v.SetInt(100)
		p(i) //=> 100
		// Set で汎用的に reflect.Value をセットできる | Type が異なる場合は panic になる
		v.Set(reflect.ValueOf(200))
		p(i) //=> 200

		p("// reflect から Interface に変換")
		if i2, ok := v.Interface().(int); ok {
			p(i2) //=> 200
		}
	}

	{
		p("/* 配列 */")

		p("// 型の取得")
		p(reflect.TypeOf([1]int{0})) //=> [1]int

		p("// 型の種別の比較")
		p(reflect.TypeOf([1]int{0}).Elem()) //=> int

		p("// 型の比較")
		p(reflect.TypeOf([1]int{0}).Kind() == reflect.Array)        //=> true
		p(reflect.TypeOf([1]int{0}) == reflect.TypeOf([1]int{100})) //=> true

		p("// 配列型の作成")
		rt := reflect.ArrayOf(1, reflect.TypeOf(0)) // 引数は要素数と要素の型
		p("Type:", rt, "Kind:", rt.Kind())          //=> Type: [1]int Kind: array

		p("// 要素数の取得")
		p(rt.Len()) //=> 1

		p("// 配列の作成と値のセット")
		// New で与えた型の値が作成されポインタが返却されるので Elem で値を取得
		rv := reflect.New(rt).Elem()
		// Index で任意の位置の要素 (reflect.Value) にアクセスできる
		p(rv.Index(0))
		// SetInt で整数値のセット
		rv.Index(0).SetInt(100)
		p(rv) //=> [100]

		p("// Interface 経由で reflect.Value から [1]int へ変換")
		p(rv.Interface().([1]int)) //=> [100]

		p("// 定義済みの変数へ値をセット")
		ary := [1]int{0}
		reflect.ValueOf(&ary).Elem().Index(0).SetInt(500)
		p(ary) //=> [500]
	}

	{
		p("/* スライス */")

		p("// 型の取得")
		p(reflect.TypeOf([]int{}))      //=> []int
		p(reflect.TypeOf(([]int)(nil))) //=> []int

		p("// 型の種別の比較")
		p(reflect.TypeOf([1]int{0}).Elem()) //=> int

		p("// 型の比較")
		p(reflect.TypeOf([]int{}).Kind() == reflect.Slice)           //=> true
		p(reflect.TypeOf([]int{}) == reflect.TypeOf([]int{1, 2, 3})) //=> true

		p("// スライス型の作成")
		rt := reflect.SliceOf(reflect.TypeOf(0)) // 引数は要素数と要素の型
		p("Type:", rt, "Kind:", rt.Kind())       //=> Type: []int Kind: slice

		p("// スライスの作成")
		// MakeSlice で要素の型、要素数、容量を指定
		rv := reflect.MakeSlice(reflect.TypeOf([]int{}), 1, 1)
		// Index で任意の位置の要素 (reflect.Value) にアクセスできる
		p(rv.Index(0))
		// SetInt で整数値のセット
		rv.Index(0).SetInt(100)
		p(rv) //=> [100]
		// Append での追加もできる
		rv = reflect.Append(rv, reflect.ValueOf(200))
		p(rv) //=> [100 200]
		// AppendSlice でスライス同士の結合もできる
		rv = reflect.AppendSlice(rv, reflect.ValueOf([]int{300}))
		p(rv) //=> [100 200 300]

		p("// Interface 経由で reflect.Value から []int へ変換")
		p(rv.Interface().([]int)) //=> [100 200 300]

		p("// 定義済みの変数へ値をセット (1)")
		s := make([]int, 2, 2)
		rv = reflect.ValueOf(&s).Elem()
		rv.Index(0).SetInt(500)
		rv.Index(1).SetInt(1000)
		p(s) //=> [500]

		p("// 定義済みの変数へ値をセット (2)")
		s = []int{}
		rv = reflect.ValueOf(&s).Elem()
		rv.Set(reflect.Append(rv, reflect.ValueOf(2000))) // Append は新しい reflect.Value を作成するので Set で元の変数を上書きする
		p(s)                                              //=> [2000]
	}

	{
		p("/* マップ */")

		p("// 型の取得")
		p(reflect.TypeOf(map[string]int{}))      //=> map[string]int
		p(reflect.TypeOf((map[string]int)(nil))) //=> map[string]int

		p("// キーの型の取得")
		p(reflect.TypeOf(map[string]int{}).Key()) //=> string

		p("// 値の型の取得")
		p(reflect.TypeOf(map[string]int{}).Elem()) //=> int
		p("// 型の比較")
		p(reflect.TypeOf(map[string]int{}).Kind() == reflect.Map)                        //=> true
		p(reflect.TypeOf(map[string]int{}) == reflect.TypeOf(map[string]int{"key1": 1})) //=> true

		p("// マップ型の作成")
		rt := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0)) // 引数はキーの型と値の型
		p("Type:", rt, "Kind:", rt.Kind())                         //=> Type: map[string]int Kind: map

		p("// マップの作成")
		rv := reflect.MakeMap(reflect.TypeOf(map[string]int{}))
		// 値のセット
		rv.SetMapIndex(reflect.ValueOf("key1"), reflect.ValueOf(100))
		// キーのリスト取得
		for _, k := range rv.MapKeys() {
			p(k)
		}
		// 値の取得
		p(rv.MapIndex(reflect.ValueOf("key1"))) //=> 100

		p("// 定義済みの変数へ値をセット")
		m := map[string]int{}
		rv = reflect.ValueOf(&m).Elem()
		rv.SetMapIndex(reflect.ValueOf("key2"), reflect.ValueOf(200))
		rv.SetMapIndex(reflect.ValueOf("key3"), reflect.ValueOf(300))
		p(m) //=> map[key2:200 key3:300]
	}

	{
		p("/* 構造体 */")

		type User struct {
			Name string
			Age  int
		}

		p("// 型の取得")
		p(reflect.TypeOf(User{})) //=> main.User

		p("// 型の比較")
		p(reflect.TypeOf(User{}).Kind() == reflect.Struct)                        //=> true
		p(reflect.TypeOf(User{}) == reflect.TypeOf(User{Name: "user1", Age: 10})) //=> true

		p("// 構造体型の作成")
		t := reflect.StructOf([]reflect.StructField{
			reflect.StructField{Name: "Name", Type: reflect.TypeOf("")},
			reflect.StructField{Name: "Age", Type: reflect.TypeOf(0)},
		})
		p(t) //=> struct { Name string; Age int }

		p("// 構造体の作成")
		rv := reflect.New(reflect.TypeOf(User{})).Elem()

		p("// フィールドの一覧")
		rt := rv.Type()
		p(rv, rt)
		for i := 0; i < rt.NumField(); i++ {
			// フィールドの取得
			f := rt.Field(i)
			// フィールド名
			p(f.Name)
			// 型
			p(f.Type)
			// タグ
			p(f.Tag)
		}

		p("// フィールドの取得")
		if f, ok := rt.FieldByName("Name"); ok {
			p(f.Name, f.Type) //=> Name string
		}

		p("// フィールドの更新")
		rv.Field(0).SetString("user1")
		p(rv.Field(0)) //=> user1

		p("// 定義済みの変数へ値をセット")
		u := User{}
		uv := reflect.ValueOf(&u).Elem()
		uv.Field(0).SetString("user2")
		uv.Field(1).SetInt(20)
		p(u.Name, u.Age) //=> user2 20
	}

	{
		p("/* 関数 */")

		fn := func(s string, i int) string {
			out := ""
			for j := 0; j < i; j++ {
				out += s
			}
			return out
		}

		p("// 型の取得")
		p(reflect.TypeOf(fn)) //=> func(string, int) string

		p("// 型の比較")
		p(reflect.TypeOf(fn).Kind() == reflect.Func)                                        //=> true
		p(reflect.TypeOf(fn) == reflect.TypeOf(func(s string, i int) string { return "" })) //=> true

		p("// 引数の一覧")
		fnt := reflect.TypeOf(fn)
		for i := 0; i < fnt.NumIn(); i++ {
			// 引数の型の取得
			p(fnt.In(i))
		}

		p("// 返り値の一覧")
		for i := 0; i < fnt.NumOut(); i++ {
			// 返り値の型の取得
			p(fnt.Out(i))
		}

		p("// 関数の実行")
		fnv := reflect.ValueOf(fn)
		out := fnv.Call([]reflect.Value{reflect.ValueOf("hello"), reflect.ValueOf(2)})
		if s, ok := out[0].Interface().(string); ok {
			p(s) //=> hellohello
		}
	}
}

func p(a ...interface{}) {
	fmt.Println(a...)
}

インターフェイス編(追記)

package main

import (
	"reflect"
	"fmt"
)

// インターフェイス宣言
type MyInterface1 interface {
	GetZero() int
}

// MyInterface1 を実装する構造体の宣言
type MyStruct1 struct{}

func (s MyStruct1) GetZero() int {
	return 0
}

// MyInterface2 を実装しない構造体の宣言
type MyStruct2 struct{}

func main() {
	p("/* インターフェイス */")

	// インターフェイスの初期化. int の 1 を interface{} にキャストする
	i1 := interface{}(1)

	// インターフェイスの値型の取得
	rv := reflect.ValueOf(i1)

	// インターフェイスであるかどうかの判定
	p(rv.CanInterface()) //=> true

	// インターフェイスの値型のタイプ型は元の int 型
	p(rv.Type()) //=> int

	// 任意のインターフェイス型の取得
	// MyInterface1 のタイプ型を取得するために nil を *MyInterface1 にキャストしてタイプ型を取得してから Elem() でポインタを外す
	// MyInterface1(nil) とすると MyInterface1 が nil を許容しないためにコンパイルエラーになる
	it := reflect.TypeOf((*MyInterface1)(nil)).Elem()

	// 任意のタイプ型がインターフェイスの実装であるかどうかの判定
	// これだけなら Type Assertion でも同様のことができるが後述するスライスの要素型の判定などをしたい場合などに応用できる
	s1 := MyStruct1{}
	s2 := MyStruct2{}
	p(reflect.TypeOf(s1).Implements(it)) //=> true
	p(reflect.TypeOf(s2).Implements(it)) //=> false

	// スライスの要素がインターフェイスの実装であるかどうかの判定
	list := []MyStruct1{}
	p(reflect.TypeOf(list).Elem().Implements(it)) //=> true
}

func p(a ...interface{}) {
	fmt.Println(a...)
}

構造体を再帰的に処理する(2019-07-11 追記)

  • 構造体のフィールドのタグの記述に従って任意の処理を行う例
  • フィールドが構造体・構造体のポインタ・構造体のスライスの場合は再帰的に処理をする
  • 例としてフィールドの値がゼロ値ならデフォルト値をセットする
  • Playgroun
package main

import (
	"fmt"
	"reflect"
	"strconv"
)

func main() {
	// 単純な構造体の場合
	type S1 struct {
		F1 string  `default:"hello"`
		F2 int     `default:"100"`
		F3 float64 `default:"1.5"`
		F4 bool    `default:"true"`
	}
	s1 := &S1{}
	structFieldRecursiveUpdater(s1, setDefaultValue)
	fmt.Printf("%v\n", s1) //=> &{hello 100 1.5 true}

	// フィールドに構造体・構造体のポインタ・構造体のスライスをもつ構造体の場合
	type S2 struct {
		F1      string `default:"hello"`
		S1      S1
		S1Ptr   *S1
		S1Slice []S1
	}
	s2 := &S2{
		S1Ptr:   &S1{},
		S1Slice: []S1{{}, {}},
	}
	structFieldRecursiveUpdater(s2, setDefaultValue)
	fmt.Printf("%v\n", s2)       //=> &{hello {hello 100 1.5 true} 0xc4202a2840 [{hello 100 1.5 true} {hello 100 1.5 true}]}
	fmt.Printf("%v\n", s2.S1Ptr) //=> &{hello 100 1.5 true}

	// フィールドに自身の構造体ポインタを持つ構造体の場合
	type S3 struct {
		F1 string `default:"hello"`
		S3 *S3
	}
	s3 := &S3{
		S3: &S3{
			S3: &S3{
				S3: &S3{},
			},
		},
	}
	structFieldRecursiveUpdater(s3, setDefaultValue)
	fmt.Printf("%v\n", s3)          //=> &{hello 0xc4200e7f80}
	fmt.Printf("%v\n", s3.S3)       //=> &{hello 0xc4200e7fa0}
	fmt.Printf("%v\n", s3.S3.S3)    //=> &{hello 0xc4200e7fc0}
	fmt.Printf("%v\n", s3.S3.S3.S3) //=> &{hello <nil>}
}

// 構造体のフィールドを再帰的に読み込んで関数 fn を実行する関数
func structFieldRecursiveUpdater(value interface{}, fn func(reflect.Value, reflect.StructTag)) {
	v := reflect.Indirect(reflect.ValueOf(value))
	t := v.Type()
	switch t.Kind() {
	case reflect.Struct:
		for i := 0; i < t.NumField(); i++ {
			ft := t.Field(i)
			fv := v.FieldByName(ft.Name)
			if ft.Type.Kind() == reflect.Struct {
				// 構造体
				structFieldRecursiveUpdater(fv.Addr().Interface(), fn)
			} else if ft.Type.Kind() == reflect.Ptr && fv.IsNil() {
				// nil ポインタなら処理しない
				continue
			} else if ft.Type.Kind() == reflect.Ptr && ft.Type.Elem().Kind() == reflect.Struct {
				// 構造体のポインタ
				structFieldRecursiveUpdater(fv.Interface(), fn)
			} else if ft.Type.Kind() == reflect.Slice && ft.Type.Elem().Kind() == reflect.Struct {
				// 構造体のスライス
				structFieldRecursiveUpdater(fv.Interface(), fn)
			} else {
				// 任意の型
				fn(fv, ft.Tag)
			}
		}
	case reflect.Slice:
		for i := 0; i < v.Len(); i++ {
			e := v.Index(i)
			structFieldRecursiveUpdater(e.Addr().Interface(), fn)
		}
	}
}

// 値がゼロ値ならタグで指定したデフォルト値をセットする関数
func setDefaultValue(v reflect.Value, tag reflect.StructTag) {
	value := tag.Get("default")
	if value == "" {
		return
	}
	if v.Interface() != reflect.Zero(v.Type()).Interface() {
		return
	}
	switch v.Kind() {
	case reflect.String:
		v.SetString(value)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if i, err := strconv.ParseInt(value, 10, 64); err == nil {
			v.SetInt(i)
		}
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		if i, err := strconv.ParseUint(value, 10, 64); err == nil {
			v.SetUint(i)
		}
	case reflect.Float32, reflect.Float64:
		if f, err := strconv.ParseFloat(value, 64); err == nil {
			v.SetFloat(f)
		}
	case reflect.Bool:
		v.SetBool(value == "true")
	}
}

宣伝

reflectパッケージについて解説した技術同人誌を作ってみたのでBoothで販売します。興味のある方はぜひご覧ください。

書籍リンク: Go言語reflectパッケージ詳解
cover.png

324
214
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
324
214

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?