Menu
Coddy logo textTech

リフレクションの基本

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 83/107。

リフレクションを使用すると、プログラムは実行時に自身の構造を検査し、操作することができます。Goの reflect パッケージはこの機能を提供し、型の検査、構造体フィールドの読み取り、およびメソッドの動的な呼び出しを可能にします。

リフレクションにおける2つの主要な型は、reflect.Typereflect.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マッピング、またはコンパイル時に型が不明な汎用ユーティリティの構築など、必要な場合にのみ控えめに使用してください。

challenge icon

チャレンジ

簡単

リフレクションを使用して任意の構造体を検査し、その構造をレポートする構造体インスペクターを作成しましょう!これは、コンパイル時には未知の型を扱うユーティリティを作成するという、リフレクションの実用的なユースケースです。

コードは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 関数が、ProductEmployee の両方の構造体を動的に処理します。

チートシート

reflect パッケージは、プログラムが実行時に自身の構造を検査および操作することを可能にします。

2つの主要な型は reflect.Typereflect.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 を呼び出し、結果を出力する

	}
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン