前書き
第1章 reflectの基礎知識
第2章 reflectの基本的な使い方
第3章 reflectの使用例・Type編
第4章 reflectの使用例・Value編
第5章 reflectの使用例・その他編
付録A reflectの型ごとの使用例
この本では、Go言語のreflectパッケージについて、解説と使い方の紹介をします。
reflectは、実行時リフレクションを実装するパッケージです。任意の値の型情報などをオブジェクト化して利用する機能を持っています。これを使用することで、あたかも動的型付け言語であるかのように、型の制約を受けずにコードが書けます。
reflectはこのように言語の根底を覆すような強力なパッケージではありますが、強力であるがゆえに、使い方を間違えると簡単に危険なコードが書けます。取り扱いには注意が必要です。
この本がreflectを使うための、あるいは不適当な場面で「使わない」と適切に判断するための助けになりますと幸いです。
第1章ではreflectパッケージとはなにか、どのような仕組みで動いているのか、どのように使われているのかといった基礎知識を紹介します。
第2章では、reflectパッケージの基本的な使用例を目的別に紹介します。
第3章では、reflectパッケージのType型のすべてのメソッドの使用例を紹介します。この章と第4章と第5章では、関数やメソッドとその使用例を羅列しています。必要になったタイミングで、辞書的にご参照ください。
第4章では、reflectパッケージのValue型のすべてのメソッドの使用例を紹介します。
第5章では、reflectパッケージのType型とValue型以外の型や、関数やメソッドの使用例を紹介します。
付録Aでは、reflectパッケージの型ごとの使用例を紹介します。reflectのオブジェクトは型によって利用できるメソッドが異なるので、型ごとに何ができるのかがわかりやすいようにまとめました。
この本のプログラムは、Goのバージョン1.14で動作確認をしました。
本書に記載された内容は、情報の提供のみを目的としています。したがって、本書を用いた開発、製作、運用は、必ずご自身の責任と判断によって行ってください。これらの情報による開発、製作、運用の結果について、著者はいかなる責任も負いません。
この章では、reflectパッケージとはなんなのか、どんな仕組みで動いているのかを紹介します。
reflectはリフレクションを実装したパッケージです。
リフレクションとは、プログラムの実行過程でプログラム自身の構造を読み取ったり、書き換えたりする技術のことです。
reflectパッケージを使うと、値の処理系内部での情報を反映したオブジェクトを取得できます。値の処理系内部での情報とはたとえば、どんな型であるかとか、メモリー上で何バイト割り当てられているかなどです。
reflectの主なオブジェクトはふたつあります。
ひとつ目は処理系内部での型についての情報が反映されたオブジェクトで、型名はreflect.Typeです。ふたつ目は処理系内部での値についての情報が反映されたオブジェクトで、型名はreflect.Valueです。
reflect.Typeは、処理系内部での型についての情報が反映されたオブジェクトです。型の名前や型の種別、パッケージのパスやメモリー上の割当バイト数などの情報をプロパティーとして持っています。
任意の値のreflect.Typeを取得するには、reflect.TypeOf関数を使います。reflect.TypeOfのシグネチャはこちらです。
func TypeOf(i interface{}) Type
引数iはinterface{}型の値であり、これは空のインターフェイスです。空のインターフェイスは実装するべきメソッドを持たないので、どんな型の値でも空のインターフェイスを実装しているとみなされ、どんな型の値でも代入できます。
引数iは情報の反映元の値であり、この値のことを本書では便宜的に「反映元の値」あるいは「元の値」と呼びます。
reflect.Typeの主な使い方は、実行時に型を判定して、型ごとに異なる処理を行うというものです。
下記のコードのisInt関数は、reflect.Typeを使って引数の型を判定し、引数が整数の場合にtrueを返します。
型の判別には、reflect.TypeのKindメソッドで取得できるreflect.Kind型の値を利用します。reflectパッケージには、整数や文字列などに対応するreflect.Kind型の定数が定義されており、引数のKindを定数のKindと比較して型を判別します。
reflect.Intなどは、符号付き整数型に対応するreflect.Kind定数です。
package main
import (
"fmt"
"reflect"
)
func isInt(v interface{}) bool {
rt := reflect.TypeOf(v)
switch rt.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return true
default:
return false
}
}
func main() {
fmt.Println(isInt(1)) //=> true
fmt.Println(isInt("1")) //=> false
fmt.Println(isInt(false)) //=> false
}
reflect.Valueは、処理系内部での値についての情報が反映されたオブジェクトであり、値の各種操作や元の値の取得、値の更新などが行えます。
値の各種操作とはたとえば、元の値がスライスのreflect.Valueに対してappendを行ったり、元の値がチャネルのreflect.Valueに送受信を行ったりすることです。
また、Typeメソッドでreflect.Typeを、Kindメソッドでreflect.Kindを取得できるので、reflect.Valueは値だけではなく、型についての情報も反映されたオブジェクトであるといえます。
任意の値のreflect.Valueを取得するには、reflect.ValueOf関数を使います。reflect.ValueOfのシグネチャはこちらです。
func ValueOf(i interface{}) Value
引数iはreflect.TypeOfの場合と同様に、どんな型の値でも代入できる空のインターフェイスです。
引数iは情報の反映元の値であり、この値のことを本書では便宜的に「反映元の値」あるいは「元の値」と呼びます。
reflect.Valueの主な使い方は、実行時に型や値を判定して値を利用した処理を行うというものです。
下記のコードのplus関数はreflect.Valueを使って引数の型を判定し、引数が整数の場合に加算を行い、引数が文字列の場合に文字列結合を行います。
reflect.Valueの取得にはreflect.ValueOf関数を用い、型の判定にはKindメソッドを利用しています。
元の値の取得は、IntメソッドやStringメソッドなど元の値の方に合わせて、適切なメソッドを利用します。
package main
import (
"fmt"
"reflect"
)
func plus(v1, v2 interface{}) interface{} {
rv1, rv2 := reflect.ValueOf(v1), reflect.ValueOf(v2)
if isInt(rv1.Kind()) && isInt(rv2.Kind()) {
return interface{}(rv1.Int() + rv2.Int())
} else if rv1.Kind() == reflect.String && rv2.Kind() == reflect.String {
return interface{}(rv1.String() + rv2.String())
}
return nil
}
func isInt(k reflect.Kind) bool {
return k == reflect.Int || k == reflect.Int8 || k == reflect.Int16 || k == reflect.Int32 || k == reflect.Int64
}
func main() {
fmt.Println(plus(100, 200)) //=> 300
fmt.Println(plus("100", "200")) //=> 100200
fmt.Println(plus(100, "200")) //=> nil
}
reflectパッケージがどのように役に立つのかを知るために、標準パッケージでの利用例を紹介します。
fmtパッケージは内部でreflectを多用しています。わかりやすい利用例として、型名や構造体のフィールド名などを表示する際には、内部でreflect.Typeなどが使われています。
package main
import "fmt"
type Animal struct {
Name string
Age int
}
func main() {
v := Animal{
Name: "dog",
Age: 3,
}
// 型名の表示
fmt.Printf("%T", v) //=> main.Animal
// フィールド名と値の表示
fmt.Printf("%+v", v) //=> {Name:dog Age:3}
}
fmt.Printfでは値をinterface{}型として受け取り、内部でreflect.Typeなどに変換し型情報を取得し、型名の表示や型に合わせた処理の分岐などを行っています。
encoding/jsonパッケージでは、reflectを使って構造体のフィールドのタグに記載された設定情報を読み取り、JSONのエンコードとデコードの際に利用しています。
package main
import (
"encoding/json"
"fmt"
"log"
)
type Animal struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
data := []byte(`{"name":"cat", "age":5}`)
v := Animal{}
if err := json.Unmarshal(data, &v); err != nil {
log.Fatal(err)
}
fmt.Println(v.Name, v.Age) //=> cat 5
}
この章では、reflectパッケージの基本的な使い方をユースケースごとに紹介します。個々の関数やメソッドごとの使用例は、第3章、第4章、第5章をご覧ください。
reflect.Typeから型名を取得するためには、NameメソッドかStringメソッドを使います。
Nameメソッドはintやstringなどの基本型や、type文で定義された型の名前を返します。スライスやマップ、構造体やポインタなどの複合型の場合は、空文字列を返します。
Stringメソッドはすべての型の名前を返します。type文で定義された型の場合は、パッケージ名もつきます。このときのパッケージ名は省略形となり、たとえばmath/randならrandとなります。
package main
import (
"fmt"
"math/rand"
"reflect"
)
func main() {
// 整数
rt := reflect.TypeOf(100)
fmt.Println(rt.Name()) //=> int
fmt.Println(rt.String()) //=> int
// スライス
rt = reflect.TypeOf([]int{})
fmt.Println(rt.Name()) //=>
fmt.Println(rt.String()) //=> []int
// 構造体
type T1 struct {
F1 int
F2 string
}
rt = reflect.TypeOf(T1{})
fmt.Println(rt.Name()) //=> T1
fmt.Println(rt.String()) //=> main.T1
// 構造体リテラル
rt = reflect.TypeOf(struct {
F1 int
F2 string
}{})
fmt.Println(rt.Name()) //=>
fmt.Println(rt.String()) //=> struct { F1 int; F2 string }
// 構造体(パッケージ名に階層がある場合)
rt = reflect.TypeOf(rand.New(rand.NewSource(99)))
fmt.Println(rt.Name()) //=>
fmt.Println(rt.String()) //=> *rand.Rand
}
reflect.Typeから型の種別を取得するためには、Kindメソッドを使います。
Kindメソッドはreflect.Kind型の値を返します。
reflectパッケージでは、整数のKindや文字列のKindなどが定数として定義されています。そのため、Typeから取得したKindを定数と比較して型の種別を判別し、型ごとの処理を実装します。
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
fmt.Println(reflect.TypeOf(0).Kind() == reflect.Int) //=> true
fmt.Println(reflect.TypeOf(uint(0)).Kind() == reflect.Uint) //=> true
fmt.Println(reflect.TypeOf(float64(0)).Kind() == reflect.Float64) //=> true
fmt.Println(reflect.TypeOf(false).Kind() == reflect.Bool) //=> true
fmt.Println(reflect.TypeOf("").Kind() == reflect.String) //=> true
fmt.Println(reflect.TypeOf([1]int{}).Kind() == reflect.Array) //=> true
fmt.Println(reflect.TypeOf([]int{}).Kind() == reflect.Slice) //=> true
fmt.Println(reflect.TypeOf(map[int]bool{}).Kind() == reflect.Map) //=> true
fmt.Println(reflect.TypeOf(make(chan int)).Kind() == reflect.Chan) //=> true
fmt.Println(reflect.TypeOf(func() {}).Kind() == reflect.Func) //=> true
type S struct{}
fmt.Println(reflect.TypeOf(S{}).Kind() == reflect.Struct) //=> true
fmt.Println(reflect.TypeOf(&S{}).Kind() == reflect.Ptr) //=> true
}