リフレクションの基本
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 83/107。
リフレクションを使用すると、プログラムは実行時に自身の構造を検査し、操作することができます。Goの reflect パッケージはこの機能を提供し、型の検査、構造体フィールドの読み取り、およびメソッドの動的な呼び出しを可能にします。
リフレクションにおける2つの主要な型は、reflect.Typeとreflect.Valueです。これらは、reflect.TypeOf()とreflect.ValueOf()を使用して取得します:
import "reflect"
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
t := reflect.TypeOf(p)
v := reflect.ValueOf(p)
fmt.Println(t.Name()) // Person
fmt.Println(t.Kind()) // struct
fmt.Println(v.NumField()) // 2構造体のフィールドを反復処理して、その名前、型、および値を確認できます。
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
fmt.Printf("%s: %v\n", field.Name, value)
}
// 出力:
// Name: Alice
// Age: 30リフレクションは強力ですが、トレードオフが伴います。コンパイル時の型チェックをバイパスし、直接アクセスよりも実行速度が遅く、コードの理解を難しくします。JSONのマーシャリング、ORMマッピング、またはコンパイル時に型が不明な汎用ユーティリティの構築など、必要な場合にのみ控えめに使用してください。
チャレンジ
簡単リフレクションを使用して任意の構造体を検査し、その構造をレポートする構造体インスペクターを作成しましょう!これは、コンパイル時には未知の型を扱うユーティリティを作成するという、リフレクションの実用的なユースケースです。
コードは2つのファイルに分けて構成します:
inspector.go: リフレクションベースの検査ユーティリティを作成します。渡された任意の構造体を検査するために
reflectパッケージを使用する関数InspectStruct(v any) stringを作成してください。この関数は、以下の内容を含むフォーマットされたレポートを返す必要があります:- 構造体の型名
- 構造体の種類("struct" である必要があります)
- フィールドの数
- 各フィールドについて:フィールド名、その型、および現在の値
出力フォーマットは以下の通りにする必要があります:
Type: [TypeName] Kind: struct Fields: [count] - [FieldName] ([FieldType]): [Value] - [FieldName] ([FieldType]): [Value] ...型情報を取得するには
reflect.TypeOf()を、フィールド値にアクセスするにはreflect.ValueOf()を使用します。メタデータについては型のNumField()とField(i)を、実際の値については値のField(i)を使用して、フィールドを反復処理します。main.go: サンプルの構造体を定義し、インスペクターを使用します。構造体の型(
productまたはemployee)とそのフィールド値を読み取り、適切な構造体を作成して検査します。productの場合:Name(string)、Price(float64)、InStock(bool) フィールドを持つProduct構造体を作成します。名前、価格、在庫状況を読み取ります。employeeの場合:ID(int)、Name(string)、Department(string) フィールドを持つEmployee構造体を作成します。ID、名前、部署を読み取ります。作成した構造体を
InspectStructに渡し、結果を出力します。
以下の入力が提供されます:
- 1行目:構造体の型(
productまたはemployee) - 続く行:その構造体型のフィールド値
例えば、以下が与えられた場合:
product
Laptop
999.99
true出力は以下のようになります:
Type: Product
Kind: struct
Fields: 3
- Name (string): Laptop
- Price (float64): 999.99
- InStock (bool): trueまた、以下が与えられた場合:
employee
42
Alice Johnson
Engineering出力は以下のようになります:
Type: Employee
Kind: struct
Fields: 3
- ID (int): 42
- Name (string): Alice Johnson
- Department (string): Engineeringさらに、以下が与えられた場合:
product
Headphones
79.50
false出力は以下のようになります:
Type: Product
Kind: struct
Fields: 3
- Name (string): Headphones
- Price (float64): 79.5
- InStock (bool): falseインスペクターが、事前に構造を知ることなく、異なる構造体型に対してどのように機能するかに注目してください。これがリフレクションの力です!同じ InspectStruct 関数が、Product と Employee の両方の構造体を動的に処理します。
チートシート
reflect パッケージは、プログラムが実行時に自身の構造を検査および操作することを可能にします。
2つの主要な型は reflect.Type と reflect.Value であり、それぞれ reflect.TypeOf() と reflect.ValueOf() を使用して取得されます。
import "reflect"
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
t := reflect.TypeOf(p)
v := reflect.ValueOf(p)
fmt.Println(t.Name()) // Person
fmt.Println(t.Kind()) // struct
fmt.Println(v.NumField()) // 2構造体のフィールドを反復処理して、その名前、型、および値を検査します。
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
fmt.Printf("%s: %v\n", field.Name, value)
}
// 出力:
// Name: Alice
// Age: 30リフレクションはコンパイル時の型チェックをバイパスし、直接アクセスよりも実行速度が遅く、コードの理解を難しくします。JSONのマーシャリング、ORMマッピング、またはコンパイル時に型が不明な汎用ユーティリティの構築などのタスクに使用してください。
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// Name、Price、および InStock フィールドを持つ Product 構造体
type Product struct {
Name string
Price float64
InStock bool
}
// ID、Name、および Department フィールドを持つ Employee 構造体
type Employee struct {
ID int
Name string
Department string
}
func main() {
reader := bufio.NewReader(os.Stdin)
// 構造体の型を読み込む
structType, _ := reader.ReadString('\n')
structType = strings.TrimSpace(structType)
if structType == "product" {
// product のフィールドを読み込む
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
priceStr, _ := reader.ReadString('\n')
priceStr = strings.TrimSpace(priceStr)
price, _ := strconv.ParseFloat(priceStr, 64)
inStockStr, _ := reader.ReadString('\n')
inStockStr = strings.TrimSpace(inStockStr)
inStock, _ := strconv.ParseBool(inStockStr)
// TODO: 読み取った値を使用して Product 構造体を作成する
// TODO: product を指定して InspectStruct を呼び出し、結果を出力する
} else if structType == "employee" {
// employee のフィールドを読み込む
idStr, _ := reader.ReadString('\n')
idStr = strings.TrimSpace(idStr)
id, _ := strconv.Atoi(idStr)
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
department, _ := reader.ReadString('\n')
department = strings.TrimSpace(department)
// TODO: 読み取った値を使用して Employee 構造体を作成する
// TODO: employee を指定して InspectStruct を呼び出し、結果を出力する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー