👨‍💻簡介

在 Go 語言中,reflect package是用來檢查和操作變數的type、value和struct。常見用法有檢察 type、調用方法,以及修改變數的value。今天簡單介紹 reflect package的主要功能、使用方法和常見用法。

主要功能

reflect package 主要用來在運行時檢查和操作變數的type訊息。這對於需要在不確定type的情況下處理資料的情況非常有用。要使用reflect package,首先需要import它:

import "reflect"

reflect package的主要功能包括:

Type reflect

reflect 可以讓你取得變數的type訊息,方便我們在運行時進行type比較,檢查變數的type。下面是一些基本的type reflect操作:

  • reflect.TypeOf:取得變數的type。
  • reflect.ValueOf:取得變數的value。
  • reflect.Zero:建立一個zero value。
package main

import (
	"fmt"
	"reflect"
)

func main() {
	var num int
	typ := reflect.TypeOf(num)
	val := reflect.ValueOf(num)
	zeroVal := reflect.Zero(typ)

	fmt.Printf("Type: %v\n", typ)            // Type: int
	fmt.Printf("Value: %v\n", val)           // Value: 0
	fmt.Printf("Zero Value: %v\n", zeroVal)  // Zero Value: 0
}

Struct reflect

reflect 可以讓你取得struct欄位的訊息,訪問struct欄位的value,以及修改struct欄位的value。

  • reflect.Value.Field:取得struct欄位的value。
  • reflect.Value.FieldByName:根據欄位名稱取得struct欄位的value。
  • reflect.Value.FieldByIndex:根據欄位索引取得struct欄位的value。
  • reflect.Value.FieldByNameFunc:使用自定義函數查找欄位。
  • reflect.Value.Set:設定變數的value。
package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name string
	Age  int
}

func main() {
	person := Person{Name: "Alan", Age: 30}
	val := reflect.ValueOf(person)

	nameField := val.FieldByName("Name")
	fmt.Printf("Name: %v\n", nameField) // Name: "Alan"

	ageField := val.FieldByName("Age")
	fmt.Printf("Age: %v\n", ageField)   // Age: 30

	val.FieldByName("Age").SetInt(31)
	fmt.Printf("Updated Age: %v\n", person.Age) // Updated Age: 31
}

方法 reflect

reflect 也可以調用struct的方法。

  • reflect.Value.Method:取得struct方法。
  • reflect.Value.MethodByName:根據方法名取得struct方法。
  • reflect.Value.Call:調用方法。
package main

import (
	"fmt"
	"reflect"
)

type Calculator struct{}

func (c Calculator) Add(a, b int) int {
	return a + b
}

func main() {

	calculator := Calculator{}

	// 使用 reflect.ValueOf 取得計算器物件的reflect value
	val := reflect.ValueOf(calculator)

	// 使用 MethodByName 方法取得名為 "Add" 的方法的reflect value
	method := val.MethodByName("Add")

	// 準備方法的參數
	args := []reflect.Value{reflect.ValueOf(5), reflect.ValueOf(3)}

	// 調用方法並取得結果,然後轉換為整數型別
	result := method.Call(args)[0].Interface().(int)

	fmt.Printf("Result: %d\n", result) // Result: 8
}

建立新的value

reflect 也可以用來建立一個新的變數,並設定它的value。先建立變數的類型,然後使用 reflect.New 方法來建立,最後使用 Elem 方法取得可設定value的對象:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	// 取得整數類型的 reflect.Type
	intType := reflect.TypeOf(0)

	// 建立一個新的整數變數
	newInt := reflect.New(intType).Elem()

	// 設定變數的value
	newInt.SetInt(42)

	// 取得變數的value
	fmt.Println("New Integer Value:", newInt.Int()) // 42
}

修改變數的value

要使用 reflect 修改變數的value,有以下步驟:

  1. 使用 reflect.ValueOf 函數取得變數的 reflect.Value
  2. 使用 .Elem() 方法取得可寫的value(如果原始value是pointer或interface)。
  3. 使用 CanSet 方法確保value可寫。
  4. 使用 SetXXX 方法設定新的value,其中 XXX 是所需的資料type。
package main

import (
	"fmt"
	"reflect"
)

func main() {
	// 創建一個整數變數
	var num int = 42

	// 使用 reflect.ValueOf 取得 reflect.Value
	valueOfNum := reflect.ValueOf(&num).Elem()

	// 使用 Elem() 取得可寫的value
	if valueOfNum.CanSet() {
		// 使用 SetInt 設定新的整數value
		valueOfNum.SetInt(99)
	}

	fmt.Println("Updated Value:", num) // 99
}

在上面的範例中,我們首先使用 reflect.ValueOf 取得整數變數 numreflect.Value,然後使用 .Elem() 方法取得可寫的value。最後,我們使用 SetInt 方法設定新的整數value。請注意,只有在可寫的value上才能使用 SetXXX 方法,使用前記得使用 CanSet 方法來確保value是可寫的。

檢查是否有效和是否為空

reflect package 還提供了 IsValidIsNil 方法,用來檢查 reflect value是否有效(不為zero value)和是否為nil pointer。這在處理 reflect value時非常有用,可以避免意外的錯誤。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var num int = 42
	value := reflect.ValueOf(num)

	if value.IsValid() {
		fmt.Println("Value is valid")
	}

	var ptr *int
	ptrValue := reflect.ValueOf(ptr)

	if ptrValue.IsValid() {
		fmt.Println("Pointer Value is valid")
	} else if ptrValue.IsNil() {
		fmt.Println("Pointer Value is nil")
	}
}

常見用法

檢查Interface的動態type

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var x interface{} = 42
	val := reflect.ValueOf(x)

	if val.Type() == reflect.TypeOf(0) {
		fmt.Printf("Value is an integer: %d\n", val.Int())
	} else {
		fmt.Printf("Value is not an integer\n")
	}
}

遍歷struct的欄位

reflect package 可以用來遍歷struct的欄位,這在某些情況下很有用,例如序列化或檢查struct的欄位屬性:

package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name    string
	Age     int
	Address string
}

func main() {
	p := Person{"Alan", 30, "123 Main St"}
	valueOfP := reflect.ValueOf(p)

	for i := 0; i < valueOfP.NumField(); i++ {
		field := valueOfP.Field(i)
		fmt.Printf("Field Name: %s, Field Value: %v\n", valueOfP.Type().Field(i).Name, field.Interface())
	}
}

檢查方法是否存在

你可以使用reflect package 檢查struct是否實現了某個方法:

package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name string
	Age  int
}

func (p Person) SayHello() {
	fmt.Println("Hello, my name is", p.Name)
}

func main() {
	p := Person{"Alan", 30}
	valueOfP := reflect.ValueOf(p)

	method := valueOfP.MethodByName("SayHello")
	if method.IsValid() {
		fmt.Println("SayHello method exists")
	} else {
		fmt.Println("SayHello method does not exist")
	}
}

操作切片和映射

reflect package 可以用來動態操作切片和映射的元素:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	slice := []int{1, 2, 3}
	mapVal := map[string]int{"a": 1, "b": 2}

	sliceValue := reflect.ValueOf(slice)
	mapValue := reflect.ValueOf(mapVal)

	// 修改切片和映射的元素
	sliceValue.Index(0).SetInt(42)
	mapValue.SetMapIndex(reflect.ValueOf("b"), reflect.ValueOf(99))

	fmt.Println(slice) // [42 2 3]
	fmt.Println(mapVal) // map[a:1 b:99]
}

📚參考資料