Reflect:Go 語言的鏡子

2023/09/19閱讀時間約 16 分鐘


raw-image

👨‍💻簡介

在 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]
}

📚參考資料

16會員
75內容數
golang
留言0
查看全部
發表第一個留言支持創作者!