Go語言 反射(reflect)及應用
基本原理及應用場景
在編譯時不知道類型的情況下,可更新變量、在運行時查看值、調用方法以及直接對它們的布局進行操作,這種機制被稱為反射。
具體的應用場景大概如下:
- 動態地獲取變量的各種信息(包括變量的類型
type、類別kind); - 如果是結構體變量,還可以獲取結構體本身的字段、方法;
- 可以修改變量的值,調用變量的方法;
具體應用場景:
-
編寫函數的適配器;
func funcName(funcPtr interface{},args ...interface{}){}在暫時未知調用哪個接口的時候,進行傳參,傳入的是可變參數
args,這時候配合傳入的函數指針funcPtr,利用反射,進行動態地調用函數。func testInt(b interface{}) { //獲取類型 rType := reflect.TypeOf(b) fmt.Println("rType:",rType) //獲取值 rVal := reflect.ValueOf(b) n := rVal.Int() fmt.Printf("rVal value:%v , type: %T\n",rVal,rVal) fmt.Printf("n value: %v , type: %T \n",n,n) //獲取interface{} Ir := rVal.Interface() fmt.Printf("Ir , value: %v , type: %T \n",Ir,Ir) //類型斷言 num := Ir.(int) fmt.Printf("num , value: %v , type: %T \n",num,num) } func testStruct(b interface{}) { rType := reflect.TypeOf(b) fmt.Println("rType:",rType) //獲取值 rVal := reflect.ValueOf(b) fmt.Printf("rVal value:%v , type: %T\n",rVal,rVal) //獲取interface{} Ir := rVal.Interface() fmt.Printf("Ir , value: %v , type: %T \n",Ir,Ir) rKind := rVal.Kind() //表示數據類別 fmt.Printf("rkind , kind: %v , type: %T \n",rKind,rKind) //類型斷言 num ,ok:= Ir.(Student) if ok { fmt.Printf("num , value: %v , type: %T \n", num, num) fmt.Println(num.Name) } } -
對結構體進行序列化,需要制定
Tag。在對函數結構體序列化的時候,自定義
Tag用到了反射,生成相對應的字符串。
reflect 包及相關常用函數
type Kind
type Kind uintKind代表Type類型值表示的具體分類。零值表示非法分類。
type Type
type Type interface { ... }Type類型用來表示一個go類型。
不是所有go類型的Type值都能使用所有方法。請參見每個方法的文檔獲取使用限制。在調用有分類限定的方法時,應先使用Kind方法獲知類型的分類。調用該分類不支持的方法會導致運行時的panic。
func TypeOf
func TypeOf(i interface{}) TypeTypeOf返回接口中保存的值的類型,TypeOf(nil)會返回nil。
type Value
type Value struct { // 內含隱藏或非導出字段 }Value為go值提供了反射接口。
不是所有go類型值的Value表示都能使用所有方法。請參見每個方法的文檔獲取使用限制。在調用有分類限定的方法時,應先使用Kind方法獲知該值的分類。調用該分類不支持的方法會導致運行時的panic。
Value類型的零值表示不持有某個值。零值的IsValid方法返回false,其Kind方法返回Invalid,而String方法返回"
",所有其它方法都會panic。絕大多數函數和方法都永遠不返回Value零值。如果某個函數/方法返回了非法的Value,它的文檔必須顯式的說明具體情況。 如果某個go類型值可以安全的用於多線程並發操作,它的Value表示也可以安全的用於並發。
func ValueOf
func ValueOf(i interface{}) ValueValueOf返回一個初始化為i接口保管的具體值的Value,ValueOf(nil)返回Value零值。
func (Value) Kind
func (v Value) Kind() KindKind返回v持有的值的分類,如果v是Value零值,返回值為Invalid
func (Value) Elem
func (v Value) Elem() ValueElem返回v持有的接口保管的值的Value封裝,或者v持有的指針指向的值的Value封裝。如果v的Kind不是Interface或Ptr會panic;如果v持有的值為nil,會返回Value零值。
unc (Value) NumField
func (v Value) NumField() int返回v持有的結構體類型值的字段數,如果v的Kind不是Struct會panic
func (Value) Field
func (v Value) Field(i int) Value返回結構體的第i個字段(的Value封裝)。如果v的Kind不是Struct或i出界會panic
func (Value) NumMethod
func (v Value) NumMethod() int返回v持有值的方法集的方法數目。
func (Value) Method
func (v Value) Method(i int) Value返回v持有值類型的第i個方法的已綁定(到v的持有值的)狀態的函數形式的Value封裝。返回值調用Call方法時不應包含接收者;返回值持有的函數總是使用v的持有者作為接收者(即第一個參數)。如果i出界,或者v的持有值是接口類型的零值(nil),會panic。
func (Value) MethodByName
func (v Value) MethodByName(name string) Value返回v的名為name的方法的已綁定(到v的持有值的)狀態的函數形式的Value封裝。返回值調用Call方法時不應包含接收者;返回值持有的函數總是使用v的持有者作為接收者(即第一個參數)。如果未找到該方法,會返回一個Value零值。
更多其它類型以及函數:Go語言標准庫文檔。
注意事項及細節
-
變量、
interface{}和reflect.Value是可以相互轉換的。
-
reflect.Value.Kind,獲取變量的類別,返回的是一個常量 -
Type和Kind的區別Type是類型,Kind是類別,Type 和Kind可能是相同的,也可能是不同的。比如:
var num int= 10,num的Type是int,Kind也是int;比如:
var stu Student stu的Type是packageXXX.Student,Kind是struct。 -
通過反射的來修改變量, 注意當使用
SetXxx方法來設置,需要通過傳入對應的指針類型來完成, 這樣才能改變傳入的變量的值;同時使用到
reflect.Value.Elem()方法轉換成對應保管的值的Value封裝,或者持有的指針指向的值的Value封裝。func testElem(b interface{}) { rVal := reflect.ValueOf(b) rVal.Elem().SetInt(20)//Elem()轉換指針為所指向的值,相當於用一個變量引用該指針指向的值 } /* func (Value) Elem eg: func (v Value) Elem() Value Elem返回v持有的接口保管的值的Value封裝,或者v持有的指針指向的值的Value封裝。 如果v的Kind不是Interface或Ptr會panic;如果v持有的值為nil,會返回Value零值。*/
實例
需求:使用反射來遍歷結構體的字段,調用結構體的方法,修改結構體字段的值,並獲取結構體標簽的值
package main
import (
"fmt"
"reflect"
)
//使用反射來遍歷結構體的字段,調用結構體的方法,修改結構體字段的值,並獲取結構體標簽的值
//定義結構體
type Student struct {
Name string `json:"name"` // 是 ` ` (tab鍵上的~按鍵) ,不是 ' '
Sex string `json:"sex"`
Age int `json:"age"`
Sal float64 `json:"sal"`
}
func (s Student) GetName() string { //第0個方法
fmt.Println("該結構體Name字段值為:",s.Name)
return s.Name
}
func (s *Student) Set(newName string,newAge int,newSal float64){ //第2個方法
s.Name = newName
s.Age = newAge
s.Sal = newSal
s.Print()
}
func (s Student) Print() { //第1個方法
fmt.Println("調用 Print 函數輸出結構體:",s)
}
//反射獲取結構體字段、方法,並調用
func testReflect(b interface{}) {
rVal := reflect.ValueOf(b).Elem()
rType := reflect.TypeOf(b).Elem()
//判斷是否是結構體在進行下一步操作
if rType.Kind() != reflect.Struct{
fmt.Println("該類型不是結構體。所以無法獲取字段及其方法。")
}
//獲取字段數量
numField := rVal.NumField()
fmt.Printf("該結構體有%d個字段\n",numField)
//遍歷字段
for i := 0; i < numField; i++ {
//獲取字段值、標簽值
rFieldTag := rType.Field(i).Tag.Get("json")
if rFieldTag != "" {
fmt.Printf("結構體第 %v 個字段值為:%v ," +
"Tag‘json’名為:%v\n",i,rVal.Field(i),rFieldTag)
}
}
//獲取方法數量
numMethod := rVal.NumMethod() //用指針可以獲取到指針接收的方法
fmt.Printf("該結構體有%d個方法\n",numMethod)
//調用方法(方法順序 按照ACSII碼排序)
rVal.Method(0).Call(nil)
rVal.Method(1).Call(nil)
//參數也需要以 Value 的切片 傳入
params := make([]reflect.Value ,3)
params[0] = reflect.ValueOf("hhhh")
params[1] = reflect.ValueOf(28)
params[2] = reflect.ValueOf(99.9)
rVal.Method(2).Call(params)
rVal.Method(1).Call(nil)
}
func main() {
stu := Student{
Name: "莉莉安",
Sex: "f",
Age: 19,
Sal: 98.5,
}
//調用編寫的函數並輸出
testReflect(&stu)
fmt.Println("主函數輸出結構體 Student :",stu)
}

上面方法無法通過調用結構體中指針接收的方法,來修改結構體字段,無法獲取指針接收的修改方法。
已解決,可選思路如下:
-
可通過直接獲取字段值進行修改。(不夠便捷)
-
用指針類型的
reflect.Value可以獲取到指針接收的方法(同時還包括值接受者的方法),不轉換為指針所指向的值,直接用指針操作即可。可以識別並使用出指針接收的結構體的所有方法,包括值接收的、指針接收的方法。(前提是原結構體有修改方法)
func (Value) Elem()
Elem返回v持有的接口保管的值的Value封裝,或者v持有的指針指向的值的Value封裝。
注意:並不是地址,或者指向原值的引用。
結合解決思路,修改結果如下:
package main
import (
"fmt"
"reflect"
)
//使用反射來遍歷結構體的字段,調用結構體的方法,修改結構體字段的值,並獲取結構體標簽的值
//定義結構體
type Student struct {
Name string `json:"name"` // 是 ` ` (tab鍵上的~按鍵) ,不是 ' '
Sex string `json:"sex"`
Age int `json:"age"`
Sal float64 `json:"sal"`
}
func (s Student) GetName() string { //第0個方法
fmt.Println("該結構體Name字段值為:",s.Name)
return s.Name
}
func (s *Student) Set(newName string,newAge int,newSal float64){ //第2個方法
s.Name = newName
s.Age = newAge
s.Sal = newSal
s.Print()
}
func (s Student) Print() { //第1個方法
fmt.Println("調用 Print 函數輸出結構體:",s)
}
//反射獲取結構體字段、方法,並調用
func testReflect(b interface{}) {
rVal := reflect.ValueOf(b).Elem()
rValI := reflect.ValueOf(b)
rType := reflect.TypeOf(b).Elem()
//判斷是否是結構體在進行下一步操作
if rType.Kind() != reflect.Struct{
fmt.Println("該類型不是結構體。所以無法獲取字段及其方法。")
}
//獲取字段數量
numField := rVal.NumField()
fmt.Printf("該結構體有%d個字段\n",numField)
//遍歷字段
for i := 0; i < numField; i++ {
//獲取字段值、標簽值
rFieldTag := rType.Field(i).Tag.Get("json")
if rFieldTag != "" {
fmt.Printf("結構體第 %v 個字段值為:%v ," +
"Tag‘json’名為:%v\n",i,rVal.Field(i),rFieldTag)
}
}
//獲取方法數量
numMethod := rValI.NumMethod() //用指針可以獲取到指針接收的方法
fmt.Printf("該結構體有%d個方法\n",numMethod)
//調用方法(方法順序 按照ACSII碼排序)
rVal.Method(0).Call(nil)
rVal.Method(1).Call(nil)
//參數也需要以 Value 的切片 傳入
params := make([]reflect.Value ,3)
params[0] = reflect.ValueOf("hhhh")
params[1] = reflect.ValueOf(28)
params[2] = reflect.ValueOf(99.9)
rValI.Method(2).Call(params)
rVal.Method(1).Call(nil)
}
func main() {
stu := Student{
Name: "莉莉安",
Sex: "f",
Age: 19,
Sal: 98.5,
}
//調用編寫的函數並輸出
testReflect(&stu)
fmt.Println("主函數輸出結構體 Student :",stu)
}

