reflectって何?
reflectは、プログラム中で動的に変数の情報を扱うためのパッケージです。型に依存しない汎用的なコードにしたり、データの操作を柔軟に行えます。性能が多少落ちるので、むやみやたらに使うべきではありませんが、非常に有用です。
どうやって使うの?
reflect.TypeOf(<変数>)で型情報を、reflect.ValueOf(<変数>)で値情報を取得します。そして、得られたreflect.Typeやreflect.Valueを使用して、必要な情報を取得したり、値を設定したりします。
Type, Value, Kind
上記で触れたType, Value, そしてKindがreflectにおいて重要な概念になります。
- Kind
Kindは、型の種類の、unit型のEnumです。ドキュメントでは以下のように説明されています。
Kind - reflect DocumentationA Kind represents the specific kind of type that a Type represents. The zero Kind is not a valid kind.
"specific kind of type"と言っているのは具体的にはBoolやIntなどです。ゼロ値は、Invalidになります。上記リンク先に記載のある、こちらを見るのが早いかと思います。
type Kind uint
const (
Invalid Kind = iota
Bool
Int
...
)
- Type
reflect.TypeOf(<変数>)で得られます。これはinterfaceであり、以下のように説明されています。
Type - reflect DocumentationType is the representation of a Go type.
Not all methods apply to all kinds of types. Restrictions, if any, are noted in the documentation for each method. Use the Kind method to find out the kind of type before calling kind-specific methods. Calling a method inappropriate to the kind of type causes a run-time panic.
Type values are comparable, such as with the == operator, so they can be used as map keys. Two Type values are equal if they represent identical types.
つまり、Typeは型を表すものであり、メソッドの使用には以下のような注意点があるということです。
- すべてのKindですべてのメソッドを使用出来るわけではない
- Kindの種類に合っていないメソッドを使用するとpanicが発生するので、Kind()メソッドで判定してから使用すること
例えば、Len()というメソッドは配列の長さを返しますが、配列でない変数でこれを実行すると、panicが発生します。どのメソッドがどの型で使用できるかはメソッドの説明に記載があります。具体例を挙げると、
arr := [3]string{}
t := reflect.TypeOf(arr)
fmt.Println(t.Len())
では1が出力されますが、
i := 3
t := reflect.TypeOf(i)
fmt.Println(t.Len())
ではpanic: reflect: Len of non-array type int
になります。この例だと明示的に型を指定していますが、型が分からない状態で使用する場合、panicになるのを避けるために、Kind()メソッドで、先にチェックする必要があります。Kind()メソッドは、先に説明したKindを返します。つまり、
if t.Kind() == reflect.Array {
fmt.Println(t.Len())
}
のようにチェックすることが出来ます。
- Value
Typeはinterfaceでしたが、Valueはstructです。
ValueValue is the reflection interface to a Go value.
Not all methods apply to all kinds of values. Restrictions, if any, are noted in the documentation for each method. Use the Kind method to find out the kind of value before calling kind-specific methods. Calling a method inappropriate to the kind of type causes a run time panic.
The zero Value represents no value. Its IsValid method returns false, its Kind method returns Invalid, its String method returns "", and all other methods panic. Most functions and methods never return an invalid value. If one does, its documentation states the conditions explicitly.
A Value can be used concurrently by multiple goroutines provided that the underlying Go value can be used concurrently for the equivalent direct operations.
To compare two Values, compare the results of the Interface method. Using == on two Values does not compare the underlying values they represent.
Typeが型を表していたのに対して、Valueは値を表します(名前のまんまですね)。
前半はTypeと言っていることは同じです。すべてのメソッドがすべてのKindで使用出来るわけではありません。例えばInt()は、int64型にして返してくれますが、intでない場合にはpanicが発生します。これもTypeと同様にKind()で先に確認するか、CanInt()など、専用のメソッドが用意されているものもあります。
"The zero Value represents~"はゼロ値についての説明になります。Valueはstructなので、ゼロ値はnilではありません。ゼロ値の場合、IsValidがfalseを返し、KindはInvalidを返し、Stinrgは""を返します。
その他、Goroutineで使用するときの話や、値の比較の注意点などが説明されています。
TypeとKind, TypeとValue
TypeとKindってどっちも型を表すけど何が違うんだっけ?TypeOfとValueOfってどっち使えばいいんだっけ?ってなりませんか?
TypeとKind
一言でいうと、Typeは独自型を含む具体的な型の名前、Kindは型の種類です。実際、TypeのString()で名前を取得できますが、string型、つまり予め定まっているものではないのに対して、KindはEnumであり、決められた種類(int、structなど)の中のどれかです。
stringやintなどのプリミティブ型では、両者は同じになります。
その他は、type Cat struct{}だと、TypeはCat, Kindはstructになります。Enumで、type CType intだと、TypeはCTypeで、Kindはintです。Goのreflectチートシートにいろいろなパターンを記載しています。
TypeとValue
名前の通りですが、reflect.Typeは型の情報、reflect.Valueは値の情報です。
var num int = 0
のようにあったとき、型というのはintのことで、valueというのは0のことです。
そのため、もしnumという変数がどんな定義か分からなくても、reflect.TypeOf(num)で型情報が、reflect.ValueOf(num)で値が0だという情報が得られます。まぁ実際、この例は意味がないです。意味があるのは、その変数の値をプログラム中で動的に処理したいときです。
-
型情報(reflect.Type)を使いたいケース
- any型の引数を受け取って、型によって処理を変えたい
- 構造体のタグ情報を取得したい
- 構造体の特定のフィールドにアクセスしたい
など
-
値情報(reflect.Value)を使いたいケース
- any型の戻り値の値を取得したい
- any型同士の値が同じが確認したい
- 構造体の全フィールドに対して処理を行いたい
など
特に構造体の例が分かりやすいので、Goのreflectチートシートのstructのところを見ていただくと、違いが理解しやすいのではと思います。
最後に
まずはreflectの基本でした。TypeにもValueにもいろいろ便利なメソッドが用意されているので、よく使うメソッドについて記事も書いていこうと思います。