本文首發於公眾號:Hunter後端
原文鏈接:Golang基礎筆記十六之反射
反射可以用於程序在運行時檢查、修改自身類型和值,主要通過 reflect 包實現。
首先,我們提出一個需求,要打印出一個結構體 struct 的各個字段及其對應的標籤數據,按照當前的筆記內容是無法解決該問題的,但是我們可以使用反射操作來完成。
以下是本篇筆記目錄:
- 變量的類型和值
- 修改變量的值
- 遍歷結構體字段
- 動態調用函數
1、變量的類型和值
先引入 reflect 模塊:
import (
"reflect"
)
我們可以通過 reflect.TypeOf() 獲取變量的類型:
var x float64 = 3.5
t := reflect.TypeOf(x)
返回的 t 是 Type 接口,我們可以進一步調用 t 的方法來獲取類型信息:
// 變量的類型名稱:
fmt.Println("x 的類型名稱是: ", t.Name())
// 判斷類型的類別:
fmt.Println("x 的類型是否是 float64: ", t.Kind() == reflect.Float64)
獲取變量的值信息:
v := reflect.ValueOf(x)
fmt.Println("value: ", v.Float() == 3.5)
2、修改變量的值
如果要修改這個變量的值,我們需要用到指針,以下是操作示例:
var x float64 = 3.5
// 這裏獲取的是變量的地址的值,如果直接 reflect.ValueOf(x) 獲取的是 x 的副本
p := reflect.ValueOf(&x)
// Elem() 方法獲取指針指向的實際值,是解引用的操作
v := p.Elem()
// 重新賦值的操作
v.SetFloat(4.9)
fmt.Println("new value: ", x)
3、遍歷結構體字段
我們先定義一個結構體如下:
type Person struct {
Id int `json:"id" form:"id"`
Name string `json:"name"`
}
打印一個 Person 示例各個字段的名稱及其值的操作如下:
p := Person{
Id: 1,
Name: "hunter",
}
t := reflect.TypeOf(p)
v := reflect.ValueOf(p)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
fmt.Printf("field_name:%s, field_type:%s, value:%v\n", field.Name, field.Type, value.Interface())
}
在這裏,我們通過 t.NumField() 方法獲取到 p 的字段個數,並使用 t.Field(i) 和 v.Field(i) 獲取到對應字段類型和值。
接着對於每個 field 和 value,我們可以打印出對應的字段名稱,字段類型和值。
我們還可以使用 field.Tag.Get() 的方式獲取到字段標籤的值:
fmt.Printf("json_tag:%s, form_tag:%s\n", field.Tag.Get("json"), field.Tag.Get("form"))
如果後續我們介紹 Golang 的 validator 模塊,可以瞭解到,validator 就是通過 struct 定義的標籤使用反射來對字段值進行驗證的。
4、動態調用函數
我們還可以使用反射來動態調用函數,比如某個函數如下:
func Add(a, b int) int {
return a + b
}
使用反射動態調用的操作如下:
func main() {
targetFunc := reflect.ValueOf(Add)
args := []reflect.Value{reflect.ValueOf(2), reflect.ValueOf(5)}
result := targetFunc.Call(args)
fmt.Println("動態調用 Add 函數,result: ", result[0].Int())
}
注意:雖然反射可以為我們提供一些便利的操作,但是代碼的可讀性和可維護性會降低,且會降低性能,需要在實際生產中謹慎使用。