Reflect:Go 語言的鏡子

閱讀時間約 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]
}

📚參考資料

avatar-img
17會員
83內容數
golang
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Alan的開發者天地 的其他內容
👨‍💻 簡介 今天的encoding/json package是我日常在開發web時很常用到的package之一,主要是用來將Go struct和 JSON 之間進行轉換。主要功能為資料序列化(marshalling)和反序列化(unmarshalling)。
👨‍💻簡介 要處理日期和時間,就必須知道這個Package -> time,Go提供了內建的timePackage。 今天主要介紹time的功能,包括時間操作、格式化等等,以及常見用法。
👨‍💻簡介 要印出Hello World,就會碰到這個Package -> fmt,今天來簡單整理一下fmt的作用、格式化的控制與常見用法。
👨‍💻簡介 昨天講到Goroutine的橋梁aka傳話筒 — Channel,那要怎麼知道對方有收到訊息,我的紙條有送到對方手上呢? 今天就是要來介紹幾種Goroutine的確定完成工作的幾種方式。
👨‍💻簡介 昨天講到Goroutine有稍微簡單介紹Channel,Channel是Go語言中極為重要的併發通訊機制,它就像是不同goroutines之間的話筒,允許它們安全地傳遞資料和信息。這個強大的工具使得Go語言在處理併發任務時非常優雅和高效。
👨‍💻簡介 在日常生活中,如果能同時做很多事情,效率肯定大大提升,那麼在Go語言中,該如何做到呢,答案就是今天的主角Goroutine了,在Go語言中,讓併發變得簡單的強大工具,今天就是來給他一個快速介紹。
👨‍💻 簡介 今天的encoding/json package是我日常在開發web時很常用到的package之一,主要是用來將Go struct和 JSON 之間進行轉換。主要功能為資料序列化(marshalling)和反序列化(unmarshalling)。
👨‍💻簡介 要處理日期和時間,就必須知道這個Package -> time,Go提供了內建的timePackage。 今天主要介紹time的功能,包括時間操作、格式化等等,以及常見用法。
👨‍💻簡介 要印出Hello World,就會碰到這個Package -> fmt,今天來簡單整理一下fmt的作用、格式化的控制與常見用法。
👨‍💻簡介 昨天講到Goroutine的橋梁aka傳話筒 — Channel,那要怎麼知道對方有收到訊息,我的紙條有送到對方手上呢? 今天就是要來介紹幾種Goroutine的確定完成工作的幾種方式。
👨‍💻簡介 昨天講到Goroutine有稍微簡單介紹Channel,Channel是Go語言中極為重要的併發通訊機制,它就像是不同goroutines之間的話筒,允許它們安全地傳遞資料和信息。這個強大的工具使得Go語言在處理併發任務時非常優雅和高效。
👨‍💻簡介 在日常生活中,如果能同時做很多事情,效率肯定大大提升,那麼在Go語言中,該如何做到呢,答案就是今天的主角Goroutine了,在Go語言中,讓併發變得簡單的強大工具,今天就是來給他一個快速介紹。
你可能也想看
Google News 追蹤
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
re 模組基本介紹 re 模組是 Python 用來處理正則表達式的標準模組。 正則表達式是一種用於描述字串模式的語法,可以用來匹配、搜尋、分割和替換字串中的特定模式。
就是指變數可以被訪問和使用的範圍,來說一下var、let和const的作用域差異。 var :function example() { console.log(x); // 輸出: undefined 因為變量提升造成的 var x = 5; } 函數作用域或全域作用域 可以重複宣告
Thumbnail
此章節旨在解釋Swift語言中函數的基本結構和操作方式,包括函數的聲明、呼叫、參數和返回值。閱讀這個章節可以幫助你理解並掌握如何在Swift編程中有效地使用和管理函數。
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
Thumbnail
本章節旨在介紹 C# 中函數的基本結構,包括訪問修飾符、返回類型、方法名稱、參數列表和方法體。同時,也介紹了函數的各種呼叫方式、參數傳遞方式和返回值類型。讀者可以通過本章節,深入理解 C# 中函數的使用和應用。
Thumbnail
內容涵蓋資料型別、型別轉換、自訂型別、元組型別、集合型別和字典型別等主題。文章首先詳述內建型別如bool、byte、char等的定義和使用,接著討論型別轉換,包括隱含轉換和明確轉換。之後文章介紹自訂型別的建立,以及元組、集合、陣列和字典型別的操作與例子。
Thumbnail
宣告變數 變數是程式中用來儲存和表示數據的標識符號​,並將變數存放在某個記憶體位子 可以用ID的方法查找變數存在哪個記憶體,此方法有利於以後查找問題用。 在大多數程式語言中,變數需要事先聲明(宣告)並賦值。 而Python是一種動態類型語言,不需要顯式宣告變數類型,而是在賦值時自動進行推斷。
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
re 模組基本介紹 re 模組是 Python 用來處理正則表達式的標準模組。 正則表達式是一種用於描述字串模式的語法,可以用來匹配、搜尋、分割和替換字串中的特定模式。
就是指變數可以被訪問和使用的範圍,來說一下var、let和const的作用域差異。 var :function example() { console.log(x); // 輸出: undefined 因為變量提升造成的 var x = 5; } 函數作用域或全域作用域 可以重複宣告
Thumbnail
此章節旨在解釋Swift語言中函數的基本結構和操作方式,包括函數的聲明、呼叫、參數和返回值。閱讀這個章節可以幫助你理解並掌握如何在Swift編程中有效地使用和管理函數。
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
Thumbnail
本章節旨在介紹 C# 中函數的基本結構,包括訪問修飾符、返回類型、方法名稱、參數列表和方法體。同時,也介紹了函數的各種呼叫方式、參數傳遞方式和返回值類型。讀者可以通過本章節,深入理解 C# 中函數的使用和應用。
Thumbnail
內容涵蓋資料型別、型別轉換、自訂型別、元組型別、集合型別和字典型別等主題。文章首先詳述內建型別如bool、byte、char等的定義和使用,接著討論型別轉換,包括隱含轉換和明確轉換。之後文章介紹自訂型別的建立,以及元組、集合、陣列和字典型別的操作與例子。
Thumbnail
宣告變數 變數是程式中用來儲存和表示數據的標識符號​,並將變數存放在某個記憶體位子 可以用ID的方法查找變數存在哪個記憶體,此方法有利於以後查找問題用。 在大多數程式語言中,變數需要事先聲明(宣告)並賦值。 而Python是一種動態類型語言,不需要顯式宣告變數類型,而是在賦值時自動進行推斷。